Skip to content

Commit

Permalink
Merge pull request #28 from hmrc/MTDSA-7111
Browse files Browse the repository at this point in the history
MTDSA added class 2 nics error to delete and amend endpoint
  • Loading branch information
ngangaNjiraini authored Oct 9, 2020
2 parents 6e3aae3 + 8d41375 commit 06075b4
Show file tree
Hide file tree
Showing 24 changed files with 125 additions and 88 deletions.
15 changes: 6 additions & 9 deletions app/v1/connectors/DeleteRetrieveConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,25 @@ import javax.inject.{Inject, Singleton}
import play.api.libs.json.Reads
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.http.HttpClient
import v1.models.request.DeleteRetrieveRequest

import scala.concurrent.{ExecutionContext, Future}

@Singleton
class DeleteRetrieveConnector @Inject()(val http: HttpClient,
val appConfig: AppConfig) extends BaseDesConnector {

def delete(request: DeleteRetrieveRequest)(
implicit hc: HeaderCarrier,
ec: ExecutionContext,
desUri: DesUri[Unit]): Future[DesOutcome[Unit]] = {
def delete()(implicit hc: HeaderCarrier,
ec: ExecutionContext,
desUri: DesUri[Unit]): Future[DesOutcome[Unit]] = {

import v1.connectors.httpparsers.StandardDesHttpParser._

delete(uri = desUri)
}

def retrieve[Resp: Reads](request: DeleteRetrieveRequest)(
implicit hc: HeaderCarrier,
ec: ExecutionContext,
desUri: DesUri[Resp]): Future[DesOutcome[Resp]] = {
def retrieve[Resp: Reads]()(implicit hc: HeaderCarrier,
ec: ExecutionContext,
desUri: DesUri[Resp]): Future[DesOutcome[Resp]] = {

import v1.connectors.httpparsers.StandardDesHttpParser._

Expand Down
4 changes: 2 additions & 2 deletions app/v1/connectors/httpparsers/StandardDesHttpParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ object StandardDesHttpParser extends HttpParser {
"[StandardDesHttpParser][read] - " +
s"Success response received from DES with correlationId: $correlationId when calling $url")
successOutcomeFactory(correlationId)
case BAD_REQUEST | NOT_FOUND | FORBIDDEN | CONFLICT => Left(ResponseWrapper(correlationId, parseErrors(response)))
case BAD_REQUEST | NOT_FOUND | FORBIDDEN | CONFLICT | UNPROCESSABLE_ENTITY => Left(ResponseWrapper(correlationId, parseErrors(response)))
case _ => Left(ResponseWrapper(correlationId, OutboundError(DownstreamError)))
}
}
}
}
1 change: 1 addition & 0 deletions app/v1/controllers/AmendDisclosuresController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class AmendDisclosuresController @Inject()(val authService: EnrolmentsAuthServic
RuleTaxYearRangeInvalidError | MtdErrorWithCustomMessage(RuleIncorrectOrEmptyBodyError.code) |
MtdErrorWithCustomMessage(SRNFormatError.code)
=> BadRequest(Json.toJson(errorWrapper))
case RuleVoluntaryClass2CannotBeChanged => Forbidden(Json.toJson(errorWrapper))
case NotFoundError => NotFound(Json.toJson(errorWrapper))
case DownstreamError => InternalServerError(Json.toJson(errorWrapper))
}
Expand Down
15 changes: 13 additions & 2 deletions app/v1/controllers/DeleteDisclosuresController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ class DeleteDisclosuresController @Inject()(val authService: EnrolmentsAuthServi

val result =
for {
parsedRequest <- EitherT.fromEither[Future](requestParser.parseRequest(rawData))
serviceResponse <- EitherT(service.delete(parsedRequest))
_ <- EitherT.fromEither[Future](requestParser.parseRequest(rawData))
serviceResponse <- EitherT(service.delete(desErrorMap))
} yield {
logger.info(
s"[${endpointLogContext.controllerName}][${endpointLogContext.endpointName}] - " +
Expand Down Expand Up @@ -102,11 +102,22 @@ class DeleteDisclosuresController @Inject()(val authService: EnrolmentsAuthServi
(errorWrapper.error: @unchecked) match {
case BadRequestError | NinoFormatError | TaxYearFormatError | RuleTaxYearNotSupportedError |
RuleTaxYearRangeInvalidError => BadRequest(Json.toJson(errorWrapper))
case RuleVoluntaryClass2CannotBeChanged => Forbidden(Json.toJson(errorWrapper))
case NotFoundError => NotFound(Json.toJson(errorWrapper))
case DownstreamError => InternalServerError(Json.toJson(errorWrapper))
}
}

private def desErrorMap: Map[String, MtdError] =
Map(
"INVALID_NINO" -> NinoFormatError,
"INVALID_TAX_YEAR" -> TaxYearFormatError,
"NOT_FOUND" -> NotFoundError,
"VOLUNTARY_CLASS2_CANNOT_BE_CHANGED" -> RuleVoluntaryClass2CannotBeChanged,
"SERVER_ERROR" -> DownstreamError,
"SERVICE_UNAVAILABLE" -> DownstreamError
)

private def auditSubmission(details: GenericAuditDetail)
(implicit hc: HeaderCarrier,
ec: ExecutionContext): Future[AuditResult] = {
Expand Down
4 changes: 2 additions & 2 deletions app/v1/controllers/RetrieveDisclosuresController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ class RetrieveDisclosuresController @Inject()(val authService: EnrolmentsAuthSer

val result =
for {
parsedRequest <- EitherT.fromEither[Future](requestParser.parseRequest(rawData))
serviceResponse <- EitherT(service.retrieve[RetrieveDisclosuresResponse](parsedRequest))
_ <- EitherT.fromEither[Future](requestParser.parseRequest(rawData))
serviceResponse <- EitherT(service.retrieve[RetrieveDisclosuresResponse]())
vendorResponse <- EitherT.fromEither[Future](
hateoasFactory
.wrap(serviceResponse.responseData, RetrieveDisclosuresHateoasData(nino, taxYear))
Expand Down
3 changes: 3 additions & 0 deletions app/v1/models/errors/mtdErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ object SRNFormatError extends MtdError("FORMAT_SRN_INVALID", "The provided schem
object RuleTaxYearNotSupportedError
extends MtdError("RULE_TAX_YEAR_NOT_SUPPORTED", "Tax year not supported, because it precedes the earliest allowable tax year")

object RuleVoluntaryClass2CannotBeChanged
extends MtdError("RULE_VOLUNTARY_CLASS2_CANNOT_BE_CHANGED", "Voluntary Class 2 NICs cannot be changed after 31st Jan following the year of submission")

object RuleIncorrectOrEmptyBodyError extends MtdError("RULE_INCORRECT_OR_EMPTY_BODY_SUBMITTED", "An empty or non-matching body was submitted")

object RuleTaxYearRangeInvalidError
Expand Down
5 changes: 3 additions & 2 deletions app/v1/services/AmendDisclosuresService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import uk.gov.hmrc.http.HeaderCarrier
import utils.Logging
import v1.connectors.AmendDisclosuresConnector
import v1.controllers.EndpointLogContext
import v1.models.errors.{DownstreamError, ErrorWrapper, NinoFormatError, NotFoundError, TaxYearFormatError}
import v1.models.errors._
import v1.models.outcomes.ResponseWrapper
import v1.models.request.disclosures.AmendDisclosuresRequest
import v1.support.DesResponseMappingSupport
Expand All @@ -49,7 +49,8 @@ class AmendDisclosuresService @Inject()(connector: AmendDisclosuresConnector) ex
"INVALID_NINO" -> NinoFormatError,
"INVALID_TAX_YEAR" -> TaxYearFormatError,
"NOT_FOUND" -> NotFoundError,
"VOLUNTARY_CLASS2_CANNOT_BE_CHANGED" -> RuleVoluntaryClass2CannotBeChanged,
"SERVER_ERROR" -> DownstreamError,
"SERVICE_UNAVAILABLE" -> DownstreamError
)
}
}
25 changes: 11 additions & 14 deletions app/v1/services/DeleteRetrieveService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,39 @@ import v1.connectors.{DeleteRetrieveConnector, DesUri}
import v1.controllers.EndpointLogContext
import v1.models.errors._
import v1.models.outcomes.ResponseWrapper
import v1.models.request.DeleteRetrieveRequest
import v1.support.DesResponseMappingSupport

import scala.concurrent.{ExecutionContext, Future}

@Singleton
class DeleteRetrieveService @Inject()(connector: DeleteRetrieveConnector) extends DesResponseMappingSupport with Logging {

def delete(request: DeleteRetrieveRequest)(
implicit hc: HeaderCarrier,
ec: ExecutionContext,
logContext: EndpointLogContext,
desUri: DesUri[Unit]): Future[Either[ErrorWrapper, ResponseWrapper[Unit]]] = {
def delete(desErrorMap: Map[String, MtdError] = defaultDesErrorMap)(implicit hc: HeaderCarrier,
ec: ExecutionContext,
logContext: EndpointLogContext,
desUri: DesUri[Unit]): Future[Either[ErrorWrapper, ResponseWrapper[Unit]]] = {

val result = for {
desResponseWrapper <- EitherT(connector.delete(request)).leftMap(mapDesErrors(desErrorMap))
desResponseWrapper <- EitherT(connector.delete()).leftMap(mapDesErrors(desErrorMap))
} yield desResponseWrapper

result.value
}

def retrieve[Resp: Format](request: DeleteRetrieveRequest)(
implicit hc: HeaderCarrier,
ec: ExecutionContext,
logContext: EndpointLogContext,
desUri: DesUri[Resp]): Future[Either[ErrorWrapper, ResponseWrapper[Resp]]] = {
def retrieve[Resp: Format](desErrorMap: Map[String, MtdError] = defaultDesErrorMap)(implicit hc: HeaderCarrier,
ec: ExecutionContext,
logContext: EndpointLogContext,
desUri: DesUri[Resp]): Future[Either[ErrorWrapper, ResponseWrapper[Resp]]] = {

val result = for {
desResponseWrapper <- EitherT(connector.retrieve[Resp](request)).leftMap(mapDesErrors(desErrorMap))
desResponseWrapper <- EitherT(connector.retrieve[Resp]()).leftMap(mapDesErrors(desErrorMap))
mtdResponseWrapper <- EitherT.fromEither[Future](validateRetrieveResponse(desResponseWrapper))
} yield mtdResponseWrapper

result.value
}

private def desErrorMap: Map[String, MtdError] =
private def defaultDesErrorMap: Map[String, MtdError] =
Map(
"INVALID_NINO" -> NinoFormatError,
"INVALID_TAX_YEAR" -> TaxYearFormatError,
Expand Down
1 change: 1 addition & 0 deletions it/v1/endpoints/AmendDisclosuresControllerISpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ class AmendDisclosuresControllerISpec extends IntegrationBaseSpec {
(BAD_REQUEST, "INVALID_NINO", BAD_REQUEST, NinoFormatError),
(BAD_REQUEST, "INVALID_TAX_YEAR", BAD_REQUEST, TaxYearFormatError),
(BAD_REQUEST, "NOT_FOUND", NOT_FOUND, NotFoundError),
(UNPROCESSABLE_ENTITY, "VOLUNTARY_CLASS2_CANNOT_BE_CHANGED", FORBIDDEN, RuleVoluntaryClass2CannotBeChanged),
(SERVICE_UNAVAILABLE, "SERVICE_UNAVAILABLE", INTERNAL_SERVER_ERROR, DownstreamError),
(INTERNAL_SERVER_ERROR, "SERVER_ERROR", INTERNAL_SERVER_ERROR, DownstreamError))

Expand Down
1 change: 1 addition & 0 deletions it/v1/endpoints/DeleteDisclosuresControllerISpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class DeleteDisclosuresControllerISpec extends IntegrationBaseSpec {
(BAD_REQUEST, "INVALID_NINO", BAD_REQUEST, NinoFormatError),
(BAD_REQUEST, "INVALID_TAX_YEAR", BAD_REQUEST, TaxYearFormatError),
(NOT_FOUND, "NOT_FOUND", NOT_FOUND, NotFoundError),
(UNPROCESSABLE_ENTITY, "VOLUNTARY_CLASS2_CANNOT_BE_CHANGED", FORBIDDEN, RuleVoluntaryClass2CannotBeChanged),
(INTERNAL_SERVER_ERROR, "SERVER_ERROR", INTERNAL_SERVER_ERROR, DownstreamError),
(SERVICE_UNAVAILABLE, "SERVICE_UNAVAILABLE", INTERNAL_SERVER_ERROR, DownstreamError))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ is:
- errors.clientOrAgentNotAuthorised
- errors.notFound
- errors.incorrectOrEmptyBody
- errors.voluntaryClass2CannotBeChanged

displayName: Create and Amend Disclosures [test only]
description: "This endpoint allows a developer to create and amend data related to disclosures, for a given tax year. A National Insurance number and tax year must be provided."
(annotations.sandboxData): !include ../../scenarios/common.md
(annotations.sandboxData): !include ../../scenarios/amendDelete.md
(annotations.scope): "write:self-assessment"
securedBy: [ sec.oauth_2_0: { scopes: [ "write:self-assessment" ] } ]
body:
Expand All @@ -32,4 +33,4 @@ responses:
headers:
X-CorrelationId:
example: c75f40a6-a3df-4429-a697-471eeec46435
description: Unique ID for operation tracking <br> String, 36 characters.
description: Unique ID for operation tracking <br> String, 36 characters.
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ is:
- errors.ruleTaxYearRangeInvalid
- errors.clientOrAgentNotAuthorised
- errors.notFound
- errors.voluntaryClass2CannotBeChanged

displayName: Delete Disclosures [test only]
description: This endpoint allows a developer to delete data related to disclosures, for a given tax year. A National Insurance number and tax year must be provided.
(annotations.sandboxData): !include ../../scenarios/common.md
(annotations.sandboxData): !include ../../scenarios/amendDelete.md
(annotations.scope): "write:self-assessment"
securedBy: [ sec.oauth_2_0: { scopes: [ "write:self-assessment" ] } ]
responses:
Expand Down
20 changes: 16 additions & 4 deletions resources/public/api/conf/1.0/errors.raml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ traits:
body:
application/json:
type: types.errorResponse
example:
description: 'The supplied income source could not be found.'
value:
code: MATCHING_RESOURCE_NOT_FOUND
examples:
notFound:
description: 'The supplied income source could not be found.'
value:
code: MATCHING_RESOURCE_NOT_FOUND
clientOrAgentNotAuthorised:
responses:
403:
Expand Down Expand Up @@ -91,3 +92,14 @@ traits:
description: 'The format of the supplied scheme reference number is not valid.'
value:
code: FORMAT_SRN_INVALID
voluntaryClass2CannotBeChanged:
responses:
403:
body:
application/json:
type: types.errorResponse
examples:
voluntaryClass2CannotBeChanged:
description: 'Voluntary Class 2 NICs cannot be changed after 31st Jan following the year of submission.'
value:
code: RULE_VOLUNTARY_CLASS2_CANNOT_BE_CHANGED
23 changes: 23 additions & 0 deletions resources/public/api/conf/1.0/scenarios/amendDelete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<p>Scenario simulations using Gov-Test-Scenario headers is only available in the sandbox environment.</p>
<table>
<thead>
<tr>
<th>Header Value (Gov-Test-Scenario)</th>
<th>Scenario</th>
</tr>
</thead>
<tbody>
<tr>
<td><p>N/A - DEFAULT</p></td>
<td><p>Simulates success response.</p></td>
</tr>
<tr>
<td><p>VOLUNTARY_CLASS2_CANNOT_BE_CHANGED</p></td>
<td><p>Simulates the scenario where Voluntary Class 2 NICs cannot be changed.</p></td>
</tr>
<tr>
<td><p>NOT_FOUND</p></td>
<td><p>Simulates the scenario where no data is found.</p></td>
</tr>
</tbody>
</table>
4 changes: 2 additions & 2 deletions resources/public/api/conf/1.0/scenarios/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
<tbody>
<tr>
<td><p>N/A - DEFAULT</p></td>
<td><p>Simulate success response.</p></td>
<td><p>Simulates success response.</p></td>
</tr>
<tr>
<td><p>NOT_FOUND</p></td>
<td><p>Simulate the scenario where no data is found.</p></td>
<td><p>Simulates the scenario where no data is found.</p></td>
</tr>
</tbody>
</table>
12 changes: 2 additions & 10 deletions test/v1/connectors/DeleteRetrieveConnectorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ package v1.connectors

import mocks.MockAppConfig
import play.api.libs.json.{Json, Reads}
import uk.gov.hmrc.domain.Nino
import v1.mocks.MockHttpClient
import v1.models.domain.DesTaxYear
import v1.models.outcomes.ResponseWrapper
import v1.models.request.DeleteRetrieveRequest

import scala.concurrent.Future

Expand All @@ -31,11 +28,6 @@ class DeleteRetrieveConnectorSpec extends ConnectorSpec {
val nino: String = "AA111111A"
val taxYear: String = "2019"

val deleteRetrieveRequest: DeleteRetrieveRequest = DeleteRetrieveRequest(
nino = Nino(nino),
taxYear = DesTaxYear(taxYear)
)

class Test extends MockHttpClient with MockAppConfig {

val connector: DeleteRetrieveConnector = new DeleteRetrieveConnector(
Expand Down Expand Up @@ -67,7 +59,7 @@ class DeleteRetrieveConnectorSpec extends ConnectorSpec {
)
.returns(Future.successful(outcome))

await(connector.delete(deleteRetrieveRequest)) shouldBe outcome
await(connector.delete()) shouldBe outcome
}
}

Expand All @@ -90,7 +82,7 @@ class DeleteRetrieveConnectorSpec extends ConnectorSpec {
)
.returns(Future.successful(outcome))

await(connector.retrieve[Data](deleteRetrieveRequest)) shouldBe outcome
await(connector.retrieve[Data]()) shouldBe outcome
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class StandardDesHttpParserSpec extends UnitSpec {
)

private def handleErrorsCorrectly[A](httpReads: HttpReads[DesOutcome[A]]): Unit =
Seq(BAD_REQUEST, NOT_FOUND, FORBIDDEN, CONFLICT).foreach(
Seq(BAD_REQUEST, NOT_FOUND, FORBIDDEN, CONFLICT, UNPROCESSABLE_ENTITY).foreach(
responseCode =>
s"receiving a $responseCode response" should {
"be able to parse a single error" in {
Expand Down
1 change: 1 addition & 0 deletions test/v1/controllers/AmendDisclosuresControllerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ class AmendDisclosuresControllerSpec
(NinoFormatError, BAD_REQUEST),
(TaxYearFormatError, BAD_REQUEST),
(NotFoundError, NOT_FOUND),
(RuleVoluntaryClass2CannotBeChanged, FORBIDDEN),
(DownstreamError, INTERNAL_SERVER_ERROR)
)

Expand Down
5 changes: 3 additions & 2 deletions test/v1/controllers/DeleteDisclosuresControllerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class DeleteDisclosuresControllerSpec
.returns(Right(requestData))

MockDeleteRetrieveService
.delete(requestData)
.delete()
.returns(Future.successful(Right(ResponseWrapper(correlationId, ()))))

val result: Future[Result] = controller.deleteDisclosures(nino, taxYear)(fakeDeleteRequest)
Expand Down Expand Up @@ -145,7 +145,7 @@ class DeleteDisclosuresControllerSpec
.returns(Right(requestData))

MockDeleteRetrieveService
.delete(requestData)
.delete()
.returns(Future.successful(Left(ErrorWrapper(Some(correlationId), mtdError))))

val result: Future[Result] = controller.deleteDisclosures(nino, taxYear)(fakeDeleteRequest)
Expand All @@ -163,6 +163,7 @@ class DeleteDisclosuresControllerSpec
(NinoFormatError, BAD_REQUEST),
(TaxYearFormatError, BAD_REQUEST),
(NotFoundError, NOT_FOUND),
(RuleVoluntaryClass2CannotBeChanged, FORBIDDEN),
(DownstreamError, INTERNAL_SERVER_ERROR)
)

Expand Down
Loading

0 comments on commit 06075b4

Please sign in to comment.