From 6e33074be353e99f16fb2bb0abe6cef393b426e6 Mon Sep 17 00:00:00 2001 From: Alen Mujezinovic Date: Wed, 31 Jan 2018 15:19:09 +0000 Subject: [PATCH 1/2] [YTA-3163] Refactor and removal of duplication --- .../epayeapi/controllers/ApiController.scala | 55 +++----- .../controllers/EpayeErrorHandler.scala | 43 ++++++ .../GetAnnualStatementController.scala | 25 ++-- .../controllers/GetEmpRefsController.scala | 15 +- .../GetMonthlyStatementController.scala | 35 ++--- .../controllers/GetSummaryController.scala | 27 ++-- .../gov/hmrc/epayeapi/modules/AppModule.scala | 1 - test/common/RestAssertions.scala | 58 ++++---- test/integration/GetEmpRefsSpec.scala | 74 +--------- .../integration/GetMonthlyStatementSpec.scala | 104 ++++++++++++++ test/integration/GetSummarySpec.scala | 126 +++++++++++++++++ test/integration/IntegrationTestBase.scala | 86 +++++++++++ .../epayeapi/controllers/GetSummarySpec.scala | 131 ----------------- .../controllers/MonthlyStatementSpec.scala | 133 ------------------ test/unit/auth/AuthComponents.scala | 32 ----- 15 files changed, 455 insertions(+), 490 deletions(-) create mode 100644 app/uk/gov/hmrc/epayeapi/controllers/EpayeErrorHandler.scala create mode 100644 test/integration/GetMonthlyStatementSpec.scala create mode 100644 test/integration/GetSummarySpec.scala create mode 100644 test/integration/IntegrationTestBase.scala delete mode 100644 test/uk/gov/hmrc/epayeapi/controllers/GetSummarySpec.scala delete mode 100644 test/uk/gov/hmrc/epayeapi/controllers/MonthlyStatementSpec.scala delete mode 100644 test/unit/auth/AuthComponents.scala diff --git a/app/uk/gov/hmrc/epayeapi/controllers/ApiController.scala b/app/uk/gov/hmrc/epayeapi/controllers/ApiController.scala index 44d8d72..6f4d8fb 100644 --- a/app/uk/gov/hmrc/epayeapi/controllers/ApiController.scala +++ b/app/uk/gov/hmrc/epayeapi/controllers/ApiController.scala @@ -17,11 +17,12 @@ package uk.gov.hmrc.epayeapi.controllers import akka.stream.Materializer +import play.api.Logger import play.api.libs.json.Json import play.api.libs.streams.Accumulator import play.api.mvc._ import uk.gov.hmrc.auth.core._ -import uk.gov.hmrc.auth.core.retrieve.{Retrieval, Retrievals} +import uk.gov.hmrc.auth.core.retrieve.Retrievals import uk.gov.hmrc.domain.EmpRef import uk.gov.hmrc.epayeapi.models.Formats._ import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson @@ -43,37 +44,22 @@ trait ApiController extends BaseController with AuthorisedFunctions { Accumulator.done { authorised(enrolment) { action(request).run() - } recover recoverAuthFailure + } recover logAuthError(recoverAuthFailure) } } - def EnrolmentsAction(enrolment: Enrolment, retrieveEnrolments: Retrieval[Enrolments])(action: Enrolments => EssentialAction): EssentialAction = { - EssentialAction { implicit request => - Accumulator.done { - authorised(enrolment.withDelegatedAuthRule("epaye-auth")) - .retrieve(retrieveEnrolments) { enrolments => - action(enrolments)(request).run() - } recover recoverAuthFailure - } - } - } + def EmpRefAction(empRefFromUrl: EmpRef)(action: EssentialAction): EssentialAction = { + val enrolment = epayeEnrolment + .withEmpRef(empRefFromUrl) + .withDelegatedAuth - def EmpRefsAction(action: Set[EmpRef] => EssentialAction): EssentialAction = - EnrolmentsAction(epayeEnrolment, epayeRetrieval) { enrolments => - EssentialAction { request => - action(enrolments.enrolments.flatMap(enrolmentToEmpRef))(request) - } - } + AuthorisedAction(enrolment)(action) + } - def EmpRefAction(empRefFromUrl: EmpRef)(action: EssentialAction): EssentialAction = { - EmpRefsAction { empRefsFromAuth => - EssentialAction { request => - empRefsFromAuth.find(_ == empRefFromUrl) match { - case Some(empRef) => action(request) - case None => Accumulator.done(invalidEmpRef) - } - } - } + def logAuthError(pf: PartialFunction[Throwable, Result]): PartialFunction[Throwable, Result] = { + case ex: Throwable => + Logger.info("Recovering from auth error:", ex) + pf(ex) } def recoverAuthFailure: PartialFunction[Throwable, Result] = { @@ -97,13 +83,14 @@ trait ApiController extends BaseController with AuthorisedFunctions { def invalidEmpRef: Result = Forbidden(Json.toJson(InvalidEmpRef)) - private def enrolmentToEmpRef(enrolment: Enrolment): Option[EmpRef] = { - for { - "IR-PAYE" <- Option(enrolment.key) - tn <- enrolment.identifiers.find(_.key == "TaxOfficeNumber") - tr <- enrolment.identifiers.find(_.key == "TaxOfficeReference") - if enrolment.isActivated - } yield EmpRef(tn.value, tr.value) + implicit class EnrolmentOps(val enrolment: Enrolment) { + def withDelegatedAuth: Enrolment = + enrolment.withDelegatedAuthRule("epaye-auth") + + def withEmpRef(empRef: EmpRef): Enrolment = + enrolment + .withIdentifier("TaxOfficeNumber", empRef.taxOfficeNumber) + .withIdentifier("TaxOfficeReference", empRef.taxOfficeReference) } } diff --git a/app/uk/gov/hmrc/epayeapi/controllers/EpayeErrorHandler.scala b/app/uk/gov/hmrc/epayeapi/controllers/EpayeErrorHandler.scala new file mode 100644 index 0000000..2379d67 --- /dev/null +++ b/app/uk/gov/hmrc/epayeapi/controllers/EpayeErrorHandler.scala @@ -0,0 +1,43 @@ +/* + * Copyright 2018 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.epayeapi.controllers + +import play.api.Logger +import play.api.libs.json.{JsError, Json} +import play.api.mvc.{Controller, Result} +import uk.gov.hmrc.epayeapi.models.Formats._ +import uk.gov.hmrc.epayeapi.models.in._ +import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson +import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson.EmpRefNotFound + +trait EpayeErrorHandler extends Controller { + def errorHandler: PartialFunction[EpayeResponse[_], Result] = { + case EpayeJsonError(err) => + Logger.error(s"epaye service returned invalid json: ${Json.prettyPrint(JsError.toJson(err))}") + InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) + case EpayeNotFound() => + Logger.info("epaye service returned a 404") + NotFound(Json.toJson(EmpRefNotFound)) + case EpayeError(status, _) => + Logger.error(s"epaye service returned unexpected response: status=$status") + InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) + case EpayeException(message) => + Logger.error(s"Caught exception while calling epaye service: message=$message") + InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) + } + +} diff --git a/app/uk/gov/hmrc/epayeapi/controllers/GetAnnualStatementController.scala b/app/uk/gov/hmrc/epayeapi/controllers/GetAnnualStatementController.scala index 0201b33..4a6a16a 100644 --- a/app/uk/gov/hmrc/epayeapi/controllers/GetAnnualStatementController.scala +++ b/app/uk/gov/hmrc/epayeapi/controllers/GetAnnualStatementController.scala @@ -19,17 +19,15 @@ package uk.gov.hmrc.epayeapi.controllers import javax.inject.{Inject, Singleton} import akka.stream.Materializer -import play.api.Logger import play.api.libs.json.Json -import play.api.mvc.{Action, EssentialAction} +import play.api.mvc.{Action, EssentialAction, Result} import uk.gov.hmrc.auth.core.AuthConnector import uk.gov.hmrc.domain.EmpRef import uk.gov.hmrc.epayeapi.connectors.{EpayeApiConfig, EpayeConnector} import uk.gov.hmrc.epayeapi.models.Formats._ import uk.gov.hmrc.epayeapi.models.TaxYear import uk.gov.hmrc.epayeapi.models.in._ -import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson.EmpRefNotFound -import uk.gov.hmrc.epayeapi.models.out.{AnnualStatementJson, ApiErrorJson} +import uk.gov.hmrc.epayeapi.models.out.AnnualStatementJson import scala.concurrent.ExecutionContext @@ -41,23 +39,20 @@ case class GetAnnualStatementController @Inject() ( implicit val ec: ExecutionContext, implicit val mat: Materializer ) - extends ApiController { + extends ApiController + with EpayeErrorHandler { def getAnnualStatement(empRef: EmpRef, taxYear: TaxYear): EssentialAction = EmpRefAction(empRef) { Action.async { request => epayeConnector.getAnnualStatement(empRef, taxYear, hc(request)).map { - case EpayeSuccess(epayeAnnualStatement) => - Ok(Json.toJson(AnnualStatementJson(config.apiBaseUrl, empRef, taxYear, epayeAnnualStatement))) - case EpayeJsonError(err) => - Logger.error(s"Upstream returned invalid json: $err") - InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) - case EpayeNotFound() => - NotFound(Json.toJson(EmpRefNotFound)) - case error: EpayeResponse[_] => - Logger.error(s"Error while fetching totals: $error") - InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) + successHandler(empRef, taxYear).orElse(errorHandler) } } } + + def successHandler(empRef: EmpRef, taxYear: TaxYear): PartialFunction[EpayeResponse[EpayeAnnualStatement], Result] = { + case EpayeSuccess(epayeAnnualStatement) => + Ok(Json.toJson(AnnualStatementJson(config.apiBaseUrl, empRef, taxYear, epayeAnnualStatement))) + } } \ No newline at end of file diff --git a/app/uk/gov/hmrc/epayeapi/controllers/GetEmpRefsController.scala b/app/uk/gov/hmrc/epayeapi/controllers/GetEmpRefsController.scala index c99617b..3edeff6 100644 --- a/app/uk/gov/hmrc/epayeapi/controllers/GetEmpRefsController.scala +++ b/app/uk/gov/hmrc/epayeapi/controllers/GetEmpRefsController.scala @@ -39,7 +39,8 @@ case class GetEmpRefsController @Inject() ( implicit val ec: ExecutionContext, implicit val mat: Materializer ) - extends ApiController { + extends ApiController + with EpayeErrorHandler { def getEmpRefs(): EssentialAction = AuthorisedAction(epayeEnrolment) { Action.async { implicit request => @@ -52,18 +53,8 @@ case class GetEmpRefsController @Inject() ( def successHandler: PartialFunction[EpayeResponse[EpayeEmpRefsResponse], Result] = { case EpayeSuccess(EpayeEmpRefsResponse(empRefs)) => + Logger.error(s"EmpRefs received: $empRefs") val empRefsJson = EmpRefsJson.fromSeq(config.apiBaseUrl, empRefs) Ok(Json.toJson(empRefsJson)) } - - def errorHandler: PartialFunction[EpayeResponse[EpayeEmpRefsResponse], Result] = { - case EpayeJsonError(err) => - Logger.error(s"Upstream returned invalid json: $err") - InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) - case EpayeNotFound() => - NotFound(Json.toJson(EmpRefNotFound)) - case error: EpayeResponse[_] => - Logger.error(s"Error while fetching totals: $error") - InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) - } } diff --git a/app/uk/gov/hmrc/epayeapi/controllers/GetMonthlyStatementController.scala b/app/uk/gov/hmrc/epayeapi/controllers/GetMonthlyStatementController.scala index 89c0d89..6ce109e 100644 --- a/app/uk/gov/hmrc/epayeapi/controllers/GetMonthlyStatementController.scala +++ b/app/uk/gov/hmrc/epayeapi/controllers/GetMonthlyStatementController.scala @@ -19,16 +19,14 @@ package uk.gov.hmrc.epayeapi.controllers import javax.inject.{Inject, Singleton} import akka.stream.Materializer -import play.api.Logger import play.api.libs.json.Json -import play.api.mvc.{Action, EssentialAction} +import play.api.mvc.{Action, EssentialAction, Result} import uk.gov.hmrc.auth.core.AuthConnector import uk.gov.hmrc.domain.EmpRef import uk.gov.hmrc.epayeapi.connectors.{EpayeApiConfig, EpayeConnector} import uk.gov.hmrc.epayeapi.models.Formats._ -import uk.gov.hmrc.epayeapi.models.in.{EpayeJsonError, EpayeNotFound, EpayeResponse, EpayeSuccess} -import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson.EmpRefNotFound -import uk.gov.hmrc.epayeapi.models.out.{ApiErrorJson, MonthlyStatementJson} +import uk.gov.hmrc.epayeapi.models.in._ +import uk.gov.hmrc.epayeapi.models.out.MonthlyStatementJson import uk.gov.hmrc.epayeapi.models.{TaxMonth, TaxYear} import scala.concurrent.ExecutionContext @@ -41,7 +39,8 @@ case class GetMonthlyStatementController @Inject() ( implicit val ec: ExecutionContext, implicit val mat: Materializer ) - extends ApiController { + extends ApiController + with EpayeErrorHandler { def getStatement(empRef: EmpRef, taxYear: TaxYear, taxMonth: TaxMonth): EssentialAction = EmpRefAction(empRef) { @@ -52,25 +51,13 @@ case class GetMonthlyStatementController @Inject() ( taxYear, taxMonth ).map { - case EpayeSuccess(json) => - Ok(Json.toJson( - MonthlyStatementJson( - config.apiBaseUrl, - empRef, - taxYear, - taxMonth, - json - ) - )) - case EpayeNotFound() => - NotFound(Json.toJson(EmpRefNotFound)) - case EpayeJsonError(error) => - Logger.error(s"Upstream returned invalid json: $error") - InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) - case error: EpayeResponse[_] => - Logger.error(s"Error while fetching totals: $error") - InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) + successHandler(empRef, taxYear, taxMonth) orElse errorHandler } } } + + def successHandler(empRef: EmpRef, taxYear: TaxYear, taxMonth: TaxMonth): PartialFunction[EpayeResponse[EpayeMonthlyStatement], Result] = { + case EpayeSuccess(json) => + Ok(Json.toJson(MonthlyStatementJson(config.apiBaseUrl, empRef, taxYear, taxMonth, json))) + } } diff --git a/app/uk/gov/hmrc/epayeapi/controllers/GetSummaryController.scala b/app/uk/gov/hmrc/epayeapi/controllers/GetSummaryController.scala index 5543aae..143c0c1 100644 --- a/app/uk/gov/hmrc/epayeapi/controllers/GetSummaryController.scala +++ b/app/uk/gov/hmrc/epayeapi/controllers/GetSummaryController.scala @@ -19,16 +19,14 @@ package uk.gov.hmrc.epayeapi.controllers import javax.inject.{Inject, Singleton} import akka.stream.Materializer -import play.api.Logger import play.api.libs.json.Json -import play.api.mvc.{Action, EssentialAction} +import play.api.mvc.{Action, EssentialAction, Result} import uk.gov.hmrc.auth.core.AuthConnector import uk.gov.hmrc.domain.EmpRef import uk.gov.hmrc.epayeapi.connectors.{EpayeApiConfig, EpayeConnector} import uk.gov.hmrc.epayeapi.models.Formats._ -import uk.gov.hmrc.epayeapi.models.in.{EpayeJsonError, EpayeNotFound, EpayeResponse, EpayeSuccess} -import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson.EmpRefNotFound -import uk.gov.hmrc.epayeapi.models.out.{ApiErrorJson, SummaryJson} +import uk.gov.hmrc.epayeapi.models.in._ +import uk.gov.hmrc.epayeapi.models.out.SummaryJson import scala.concurrent.ExecutionContext @@ -40,23 +38,20 @@ case class GetSummaryController @Inject() ( implicit val ec: ExecutionContext, implicit val mat: Materializer ) - extends ApiController { + extends ApiController + with EpayeErrorHandler { def getSummary(empRef: EmpRef): EssentialAction = EmpRefAction(empRef) { Action.async { request => epayeConnector.getTotal(empRef, hc(request)).map { - case EpayeSuccess(totals) => - Ok(Json.toJson(SummaryJson(config.apiBaseUrl, empRef, totals))) - case EpayeJsonError(err) => - Logger.error(s"Upstream returned invalid json: $err") - InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) - case EpayeNotFound() => - NotFound(Json.toJson(EmpRefNotFound)) - case error: EpayeResponse[_] => - Logger.error(s"Error while fetching totals: $error") - InternalServerError(Json.toJson(ApiErrorJson.InternalServerError)) + successHandler(empRef) orElse errorHandler } } } + + def successHandler(empRef: EmpRef): PartialFunction[EpayeResponse[EpayeTotalsResponse], Result] = { + case EpayeSuccess(totals) => + Ok(Json.toJson(SummaryJson(config.apiBaseUrl, empRef, totals))) + } } diff --git a/app/uk/gov/hmrc/epayeapi/modules/AppModule.scala b/app/uk/gov/hmrc/epayeapi/modules/AppModule.scala index e8b2a8d..19c1cf5 100644 --- a/app/uk/gov/hmrc/epayeapi/modules/AppModule.scala +++ b/app/uk/gov/hmrc/epayeapi/modules/AppModule.scala @@ -32,7 +32,6 @@ import uk.gov.hmrc.play.config.ServicesConfig class AppModule() extends AbstractModule { def configure(): Unit = { bind(classOf[PlayAuthConnector]).to(classOf[MicroserviceAuthConnector]).asEagerSingleton() -// bind(classOf[WSHttp]).to(classOf[WSHttp]).asEagerSingleton() bind(classOf[Startup]).to(classOf[AppStartup]).asEagerSingleton() } diff --git a/test/common/RestAssertions.scala b/test/common/RestAssertions.scala index 763a7ae..61af954 100644 --- a/test/common/RestAssertions.scala +++ b/test/common/RestAssertions.scala @@ -67,38 +67,40 @@ trait BaseClientGivens[A <: BaseClientGivens[A]] { self: A => } def when()(implicit wsClient: WSClient): When = new When(wsClient) def and(): A = this -} -class ClientGivens extends BaseClientGivens[ClientGivens] { + def epayeEmpRefsEndpointReturns(response: String): A = { + epayeEmpRefsEndpointReturns(200, response) + } - def isAuthorized: ClientGivens = { + def epayeEmpRefsEndpointReturns(status: Int, response: String): A = { stubFor { - post(urlPathEqualTo(s"/auth/authorise")) + get(urlPathEqualTo(s"/epaye/self/api/v1/emprefs")) .willReturn { aResponse() - .withBody(Fixtures.authorised) - .withStatus(200) + .withBody(response) + .withHeader("Content-Type", "application/json") + .withStatus(status) } } this } +} - def epayeEmpRefsEndpointReturns(response: String): ClientGivens = { - epayeEmpRefsEndpointReturns(200, response) - } +class ClientGivens extends BaseClientGivens[ClientGivens] { - def epayeEmpRefsEndpointReturns(status: Int, response: String): ClientGivens = { + def isAuthorized: ClientGivens = { stubFor { - get(urlPathEqualTo(s"/epaye/self/api/v1/emprefs")) + post(urlPathEqualTo(s"/auth/authorise")) .willReturn { aResponse() - .withBody(response) - .withHeader("Content-Type", "application/json") - .withStatus(status) + .withBody(Fixtures.authorised) + .withStatus(200) } } this } + + } class ClientWithEmpRefGivens(empRef: EmpRef) extends BaseClientGivens[ClientWithEmpRefGivens] { @@ -118,30 +120,38 @@ class ClientWithEmpRefGivens(empRef: EmpRef) extends BaseClientGivens[ClientWith } def epayeAnnualStatementReturns(body: String): ClientWithEmpRefGivens = { + epayeAnnualStatementReturns(200, body) + } + + def epayeAnnualStatementReturns(status: Int, body: String): ClientWithEmpRefGivens = { stubFor( get( urlPathEqualTo(s"/epaye/${empRef.encodedValue}/api/v1/annual-statement") ).willReturn( - aResponse() - .withBody(body) - .withHeader("Content-Type", "application/json") - .withStatus(200) - ) + aResponse() + .withBody(body) + .withHeader("Content-Type", "application/json") + .withStatus(status) + ) ) this } def epayeMonthlyStatementReturns(body: String): ClientWithEmpRefGivens = { + epayeMonthlyStatementReturns(200, body) + } + + def epayeMonthlyStatementReturns(status: Int, body: String): ClientWithEmpRefGivens = { stubFor( get( urlPathEqualTo(s"/epaye/${empRef.encodedValue}/api/v1/monthly-statement") ).willReturn( - aResponse() - .withBody(body) - .withHeader("Content-Type", "application/json") - .withStatus(200) - ) + aResponse() + .withBody(body) + .withHeader("Content-Type", "application/json") + .withStatus(status) + ) ) this diff --git a/test/integration/GetEmpRefsSpec.scala b/test/integration/GetEmpRefsSpec.scala index 98c8b77..2d2f545 100644 --- a/test/integration/GetEmpRefsSpec.scala +++ b/test/integration/GetEmpRefsSpec.scala @@ -16,28 +16,13 @@ package integration -import common._ -import org.scalatest.{Matchers, WordSpec} -import play.api.Application -import play.api.inject.bind -import play.api.inject.guice.GuiceApplicationBuilder import play.api.libs.json.Json -import play.api.routing.Router +import uk.gov.hmrc.epayeapi.models.Formats._ import uk.gov.hmrc.epayeapi.models.in.EpayeEmpRefsResponse import uk.gov.hmrc.epayeapi.models.out.{ApiErrorJson, EmpRefsJson} -import uk.gov.hmrc.epayeapi.router.RoutesProvider -import uk.gov.hmrc.epayeapi.models.Formats._ class GetEmpRefsSpec - extends WordSpec - with Matchers - with WSClientSetup - with WiremockSetup - with EmpRefGenerator - with RestAssertions { - - override implicit lazy val app: Application = - new GuiceApplicationBuilder().overrides(bind[Router].toProvider[RoutesProvider]).build() + extends IntegrationTestBase { trait Setup { val url = baseUrl @@ -60,6 +45,7 @@ class GetEmpRefsSpec .bodyIsOfJson(Json.toJson(EmpRefsJson.fromSeq(apiBaseUrl, empRefs))) } + "return 500 Internal Server error if upstream returns invalid JSON" in new Setup { given() .client.isAuthorized @@ -100,57 +86,9 @@ class GetEmpRefsSpec .bodyIsOfJson(Json.toJson(ApiErrorJson.InternalServerError)) } } + } - "return 401 Unauthorized with invalid bearer token" in new Setup { - given() - .client.isInvalidBearerToken - .and() - .epayeEmpRefsEndpointReturns(empRefJson) - .when() - .get(url) - .withAuthHeader() - .thenAssertThat() - .statusCodeIs(401) - .bodyIsOfJson(Json.toJson(ApiErrorJson.InvalidBearerToken)) - } - - "return 401 Unauthorized with missing bearer token" in new Setup { - given() - .client.isMissingBearerToken - .and() - .epayeEmpRefsEndpointReturns(empRefJson) - .when() - .get(url) - .withAuthHeader() - .thenAssertThat() - .statusCodeIs(401) - .bodyIsOfJson(Json.toJson(ApiErrorJson.MissingBearerToken)) - } - - "return 401 Unauthorized with an expired bearer token" in new Setup { - given() - .client.isBearerTokenExpired - .and() - .epayeEmpRefsEndpointReturns(empRefJson) - .when() - .get(url) - .withAuthHeader() - .thenAssertThat() - .statusCodeIs(401) - .bodyIsOfJson(Json.toJson(ApiErrorJson.ExpiredBearerToken)) - } - - "return 401 Unauthorized with other auth errors" in new Setup { - given() - .client.isUnsupportedAuthProvider - .and() - .epayeEmpRefsEndpointReturns(empRefJson) - .when() - .get(url) - .withAuthHeader() - .thenAssertThat() - .statusCodeIs(401) - .bodyIsOfJson(Json.toJson(ApiErrorJson.AuthorisationError)) - } + it should new Setup { + haveAuthentication(url) } } diff --git a/test/integration/GetMonthlyStatementSpec.scala b/test/integration/GetMonthlyStatementSpec.scala new file mode 100644 index 0000000..6865722 --- /dev/null +++ b/test/integration/GetMonthlyStatementSpec.scala @@ -0,0 +1,104 @@ +/* + * Copyright 2018 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 integration + +import play.api.libs.json.Json +import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson +import uk.gov.hmrc.epayeapi.models.Formats._ + +import scala.io.Source + + +class GetMonthlyStatementSpec + extends IntegrationTestBase { + + "The monthly statement endpoint" should { + "return 200 OK with the found statement" in new Setup { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeMonthlyStatementReturns(getResourceAsString("/epaye/monthly-statement/in/2017-3.json")) + .when() + .get(url) + .thenAssertThat() + .statusCodeIs(200) + .bodyIsOfJson(Json.parse( + getResourceAsString("/epaye/monthly-statement/out/2017-3.json") + .replaceAllLiterally("%{ton}", empRef.taxOfficeNumber) + .replaceAllLiterally("%{tor}", empRef.taxOfficeReference) + .replaceAllLiterally("%{apiBaseUrl}", apiBaseUrl) + )) + } + + "return 500 Internal Server Error if upstream returns invalid JSON" in new Setup { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeMonthlyStatementReturns("{not json}") + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(500) + .bodyIsOfJson(Json.toJson(ApiErrorJson.InternalServerError)) + } + + "return 404 Not Found if upstream returns a 404" in new Setup { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeMonthlyStatementReturns(404, "") + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(404) + .bodyIsOfJson(Json.toJson(ApiErrorJson.EmpRefNotFound)) + } + "return a 500 Internal Server Error on errors from upstream" in new Setup { + for (status <- Seq(400, 401, 402, 403, 502, 503)) { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeMonthlyStatementReturns(status, "") + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(500) + .bodyIsOfJson(Json.toJson(ApiErrorJson.InternalServerError)) + } + } + } + + it should new Setup { + haveAuthentication(url) + } + + trait Setup { + val empRef = getEmpRef + val url = s"$baseUrl/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements/2017-18/3" + val apiBaseUrl = app.configuration.underlying.getString("api.baseUrl") + + def getResourceAsString(name: String): String = + Source.fromURL(getClass.getResource(name), "utf-8").mkString("") + + // def getResourceAsJson(name: String): JsValue = + // Json.parse(getResourceAsString(name)) + // } + } +} diff --git a/test/integration/GetSummarySpec.scala b/test/integration/GetSummarySpec.scala new file mode 100644 index 0000000..66e14c9 --- /dev/null +++ b/test/integration/GetSummarySpec.scala @@ -0,0 +1,126 @@ +/* + * Copyright 2018 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 integration + +import play.api.libs.json.Json +import uk.gov.hmrc.epayeapi.models.Formats._ +import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson + +class GetSummarySpec + extends IntegrationTestBase { + + "The summary endpoint" should { + "return 200 OK on active enrolments" in new Setup { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeAnnualStatementReturns( + """ + |{ + | "rti": { + | "totals": { + | "balance": 100 + | } + | }, + | "nonRti": { + | "totals": { + | "balance": 23 + | } + | } + |} + """.stripMargin + ) + .when() + .get(url) + .thenAssertThat() + .statusCodeIs(200) + .bodyIsOfJson(Json.parse( + s""" + |{ + | "outstandingCharges": { + | "amount": 123, + | "breakdown": { + | "rti": 100, + | "nonRti": 23 + | } + | }, + | "_links" : { + | "empRefs": { + | "href": "$apiBaseUrl/organisations/paye/" + | }, + | "self": { + | "href": "$apiBaseUrl/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}" + | } + | } + |} + """.stripMargin + )) + + } + + "return 500 Internal Server Error if upstream returns invalid JSON" in new Setup { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeAnnualStatementReturns("{not json}") + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(500) + .bodyIsOfJson(Json.toJson(ApiErrorJson.InternalServerError)) + } + + "return 404 Not Found if upstream returns a 404" in new Setup { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeAnnualStatementReturns(404, "") + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(404) + .bodyIsOfJson(Json.toJson(ApiErrorJson.EmpRefNotFound)) + } + + "return a 500 Internal Server Error on errors from upstream" in new Setup { + for (status <- Seq(400, 401, 402, 403, 502, 503)) { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeAnnualStatementReturns(status, "") + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(500) + .bodyIsOfJson(Json.toJson(ApiErrorJson.InternalServerError)) + } + } + } + + it should new Setup { + haveAuthentication(url) + } + + trait Setup { + val empRef = getEmpRef + val url = s"$baseUrl/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}" + val apiBaseUrl = app.configuration.underlying.getString("api.baseUrl") + } +} diff --git a/test/integration/IntegrationTestBase.scala b/test/integration/IntegrationTestBase.scala new file mode 100644 index 0000000..dac5527 --- /dev/null +++ b/test/integration/IntegrationTestBase.scala @@ -0,0 +1,86 @@ +/* + * Copyright 2018 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 integration + +import common._ +import org.scalatest.{Matchers, WordSpec} +import play.api.Application +import play.api.inject.bind +import play.api.inject.guice.GuiceApplicationBuilder +import play.api.libs.json.Json +import play.api.routing.Router +import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson +import uk.gov.hmrc.epayeapi.models.Formats._ +import uk.gov.hmrc.epayeapi.router.RoutesProvider + +trait IntegrationTestBase + extends WordSpec + with Matchers + with WSClientSetup + with WiremockSetup + with EmpRefGenerator + with RestAssertions { + + def haveAuthentication(url: String) = { + "return 401 Unauthorized with invalid bearer token" in { + given() + .client.isInvalidBearerToken + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(401) + .bodyIsOfJson(Json.toJson(ApiErrorJson.InvalidBearerToken)) + } + + "return 401 Unauthorized with missing bearer token" in { + given() + .client.isMissingBearerToken + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(401) + .bodyIsOfJson(Json.toJson(ApiErrorJson.MissingBearerToken)) + } + + "return 401 Unauthorized with an expired bearer token" in { + given() + .client.isBearerTokenExpired + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(401) + .bodyIsOfJson(Json.toJson(ApiErrorJson.ExpiredBearerToken)) + } + + "return 401 Unauthorized with other auth errors" in { + given() + .client.isUnsupportedAuthProvider + .when() + .get(url) + .withAuthHeader() + .thenAssertThat() + .statusCodeIs(401) + .bodyIsOfJson(Json.toJson(ApiErrorJson.AuthorisationError)) + } + } + + override implicit lazy val app: Application = + new GuiceApplicationBuilder().overrides(bind[Router].toProvider[RoutesProvider]).build() +} diff --git a/test/uk/gov/hmrc/epayeapi/controllers/GetSummarySpec.scala b/test/uk/gov/hmrc/epayeapi/controllers/GetSummarySpec.scala deleted file mode 100644 index 1335310..0000000 --- a/test/uk/gov/hmrc/epayeapi/controllers/GetSummarySpec.scala +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2018 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.epayeapi.controllers - -import org.mockito.Matchers -import org.mockito.Matchers._ -import org.mockito.Mockito.{reset, when} -import org.scalatest.BeforeAndAfterEach -import play.api.Application -import play.api.inject.bind -import play.api.libs.json.Json -import play.api.mvc.Result -import play.api.test.FakeRequest -import play.api.test.Helpers._ -import uk.gov.hmrc.auth.core.{ConfidenceLevel, Enrolment, EnrolmentIdentifier} -import uk.gov.hmrc.domain.EmpRef -import uk.gov.hmrc.epayeapi.config.WSHttp -import uk.gov.hmrc.epayeapi.connectors.EpayeApiConfig -import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse} -import unit.AppSpec -import unit.auth.AuthComponents.AuthOk - -import scala.concurrent.Future -import scala.concurrent.Future.successful - -class GetSummarySpec extends AppSpec with BeforeAndAfterEach { - val ton = EnrolmentIdentifier("TaxOfficeNumber", "001") - val tor = EnrolmentIdentifier("TaxOfficeReference", "AB00001") - val empRef = EmpRef(ton.value, tor.value) - - implicit val hc = HeaderCarrier() - val http = mock[WSHttp] - - val app = builder - .update(_.overrides(bind(classOf[WSHttp]).toInstance(http))) - - val activeEnrolment = - AuthOk(Enrolment("IR-PAYE", Seq(ton, tor), "activated", Some("300"))) - - val inactiveEnrolment = - AuthOk(activeEnrolment.data.copy(state = "inactive")) - - val differentEnrolment = - AuthOk(Enrolment("IR-Else", Seq(ton, tor), "activated", Some("300"))) - - def config(implicit a: Application): EpayeApiConfig = - inject[EpayeApiConfig] - - def request(implicit a: Application): Future[Result] = - inject[GetSummaryController].getSummary(empRef)(FakeRequest()) - - - override protected def beforeEach(): FixtureParam = { - reset(http) - } - - "The summary endpoint" should { - "return 200 OK on active enrolments" in new App(app.withAuth(activeEnrolment).build) { - val firstUrl = s"${config.epayeBaseUrl}" + - s"/epaye" + - s"/${empRef.encodedValue}" + - s"/api/v1/annual-statement" - - when(http.GET[HttpResponse](Matchers.eq(firstUrl))(anyObject(), anyObject(), anyObject())).thenReturn { - successful { - HttpResponse(OK, responseString = Some( - """ - |{ - | "rti": { - | "totals": { - | "balance": 100 - | } - | }, - | "nonRti": { - | "totals": { - | "balance": 23 - | } - | } - |} - """.stripMargin - )) - } - } - - contentAsString(request) shouldBe Json.parse( - s""" - |{ - | "outstandingCharges": { - | "amount": 123, - | "breakdown": { - | "rti": 100, - | "nonRti": 23 - | } - | }, - | "_links" : { - | "empRefs": { - | "href": "$apiBaseUrl/organisations/paye/" - | }, - | "self": { - | "href": "$apiBaseUrl/organisations/paye/001/AB00001" - | } - | } - |} - """.stripMargin).toString() - status(request) shouldBe OK - } - - "return 403 Forbidden on inactive enrolments" in new App(app.withAuth(inactiveEnrolment).build) { - status(request) shouldBe FORBIDDEN - } - "return 403 Forbidden with different enrolments" in new App(app.withAuth(differentEnrolment).build) { - status(request) shouldBe FORBIDDEN - } - } - - -} diff --git a/test/uk/gov/hmrc/epayeapi/controllers/MonthlyStatementSpec.scala b/test/uk/gov/hmrc/epayeapi/controllers/MonthlyStatementSpec.scala deleted file mode 100644 index 768744b..0000000 --- a/test/uk/gov/hmrc/epayeapi/controllers/MonthlyStatementSpec.scala +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2018 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.epayeapi.controllers - -import akka.util.ByteString -import org.mockito.Matchers._ -import org.mockito.Mockito.reset -import org.mockito.{Matchers, Mockito} -import org.scalatest.BeforeAndAfterEach -import play.api.Application -import play.api.inject.bind -import play.api.libs.json.{JsValue, Json} -import play.api.libs.streams.Accumulator -import play.api.mvc.Result -import play.api.test.FakeRequest -import play.api.test.Helpers.{status, _} -import uk.gov.hmrc.auth.core.{Enrolment, EnrolmentIdentifier} -import uk.gov.hmrc.domain.EmpRef -import uk.gov.hmrc.epayeapi.config.WSHttp -import uk.gov.hmrc.epayeapi.connectors.EpayeApiConfig -import uk.gov.hmrc.epayeapi.models.{TaxMonth, TaxYear} -import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse} -import unit.AppSpec -import unit.auth.AuthComponents.AuthOk - -import scala.concurrent.Future -import scala.concurrent.Future.successful -import scala.io.Source - -class MonthlyStatementSpec extends AppSpec with BeforeAndAfterEach { - val ton = EnrolmentIdentifier("TaxOfficeNumber", "921") - val tor = EnrolmentIdentifier("TaxOfficeReference", "PE91702") - - val empRef = EmpRef(ton.value, tor.value) - val taxYear = TaxYear(2017) - val taxMonth = TaxMonth(taxYear, 3) - - implicit val hc = HeaderCarrier() - val http = mock[WSHttp] - val app = - builder - .update { _.overrides(bind(classOf[WSHttp]).toInstance(http)) } - - val activeEnrolment = AuthOk(Enrolment("IR-PAYE", Seq(ton, tor), "activated", Some("300"))) - val inactiveEnrolment = AuthOk(activeEnrolment.data.copy(state = "inactive")) - val differentEnrolment = AuthOk(Enrolment("IR-Else", Seq(ton, tor), "activated", Some("300"))) - - def config(implicit a: Application): EpayeApiConfig = - inject[EpayeApiConfig] - - def request(implicit a: Application): Accumulator[ByteString, Result] = - inject[GetMonthlyStatementController].getStatement(empRef, taxYear, taxMonth)(FakeRequest()) - - override protected def beforeEach(): FixtureParam = { - reset(http) - } - - "The MonthlyStatement endpoint" should { - "return 403 Forbidden on inactive enrolments" in new App(app.withAuth(inactiveEnrolment).build) { - status(request) shouldBe FORBIDDEN - } - "return 403 Forbidden with different enrolments" in new App(app.withAuth(differentEnrolment).build) { - status(request) shouldBe FORBIDDEN - } - "return 404 NotFound when the statements are not found" in new App(app.withAuth(activeEnrolment).build) { - val epayeUrl = - s"${config.epayeBaseUrl}" + - s"/epaye/${empRef.encodedValue}" + - s"/api/v1" + - s"/monthly-statement/${taxYear.asString}/${taxMonth.asString}" - Mockito.when(http.GET[HttpResponse](Matchers.eq(epayeUrl))(anyObject(), anyObject(), anyObject())).thenReturn { - successful { - HttpResponse(NOT_FOUND) - } - } - - status(request) shouldBe NOT_FOUND - } - "return 200 OK with the found statement" in new App(app.withAuth(activeEnrolment).build) { - val inputJsonString: String = getResourceAsString("/epaye/monthly-statement/in/2017-3.json") - val epayeUrl = - s"${config.epayeBaseUrl}" + - s"/epaye/${empRef.encodedValue}" + - s"/api/v1" + - s"/monthly-statement/${taxYear.asString}/${taxMonth.asString}" - Mockito.when(http.GET[HttpResponse](Matchers.eq(epayeUrl))(anyObject(), anyObject(), anyObject())).thenReturn { - successful { - HttpResponse(OK, responseString = Some(inputJsonString)) - } - } - - status(request) shouldBe OK - val expectedJson = - Json.parse( - getResourceAsString("/epaye/monthly-statement/out/2017-3.json") - .replaceAllLiterally("%{ton}", empRef.taxOfficeNumber) - .replaceAllLiterally("%{tor}", empRef.taxOfficeReference) - .replaceAllLiterally("%{apiBaseUrl}", apiBaseUrl) - ) - contentAsJson(request) shouldBe expectedJson - } - } - - def contentAsPrettyJson(result: Future[play.api.mvc.Result]): String = { - Json.prettyPrint(contentAsJson(result)) - } - - def prettyPrint(string: String): String = - prettyPrint(Json.parse(string)) - - def prettyPrint(json: JsValue): String = - Json.prettyPrint(json) - - def getResourceAsString(name: String): String = - Source.fromURL(getClass.getResource(name), "utf-8").mkString("") - - def getResourceAsJson(name: String): JsValue = - Json.parse(getResourceAsString(name)) -} diff --git a/test/unit/auth/AuthComponents.scala b/test/unit/auth/AuthComponents.scala deleted file mode 100644 index 3f24270..0000000 --- a/test/unit/auth/AuthComponents.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2018 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 unit.auth - -import uk.gov.hmrc.auth.core.{AuthorisationException, Enrolment, Enrolments} -import uk.gov.hmrc.epayeapi.connectors.stub.FakeAuthConnector - -object AuthComponents { - case class AuthOk(data: Enrolment) extends FakeAuthConnector { - override val success = Enrolments(Set(data)) - } - case class AuthFail(pureException: AuthorisationException) extends FakeAuthConnector { - override val exception: Option[AuthorisationException] = Some(pureException) - } - case class AnyAuth[A](data: A) extends FakeAuthConnector { - override val success = data - } -} From 713926b04f5bb27ef660e8559354203ae1fdc140 Mon Sep 17 00:00:00 2001 From: Alen Mujezinovic Date: Wed, 31 Jan 2018 15:48:21 +0000 Subject: [PATCH 2/2] [YTA-3163] Amended API documentation based on SDST feedback --- .../gov/hmrc/epayeapi/router/ApiRouter.scala | 46 +++++----- .../public/api/conf/1.0/application.raml | 4 +- .../api/conf/1.0/documentation/overview.md | 2 +- .../conf/1.0/schemas/Definitions.schema.json | 20 +++-- .../hmrc/epayeapi/router/ApiRouterTest.scala | 88 +++++++++++++++++++ 5 files changed, 128 insertions(+), 32 deletions(-) create mode 100644 test/uk/gov/hmrc/epayeapi/router/ApiRouterTest.scala diff --git a/app/uk/gov/hmrc/epayeapi/router/ApiRouter.scala b/app/uk/gov/hmrc/epayeapi/router/ApiRouter.scala index eb76636..587b9f6 100644 --- a/app/uk/gov/hmrc/epayeapi/router/ApiRouter.scala +++ b/app/uk/gov/hmrc/epayeapi/router/ApiRouter.scala @@ -15,7 +15,7 @@ */ package uk.gov.hmrc.epayeapi.router - +import ApiRouter._ import javax.inject.{Inject, Singleton} import play.api.routing.Router.Routes @@ -34,28 +34,6 @@ case class ApiRouter @Inject() ( getMonthlyStatementController: GetMonthlyStatementController ) extends SimpleRouter { - object TaxOfficeNumber { - val regex = "([0-9a-zA-Z]{3})".r - def unapply(string: String): Option[String] = { - string match { - case regex(result) => Some(result) - case _ => None - } - } - } - - - object TaxOfficeReference { - val regex = "([0-9a-zA-Z]{7,10})".r - def unapply(string: String): Option[String] = { - string match { - case regex(result) => Some(result) - case _ => None - } - } - } - - val appRoutes = Router.from { case GET(p"/") => getEmpRefsController.getEmpRefs() @@ -73,3 +51,25 @@ case class ApiRouter @Inject() ( val routes: Routes = prodRoutes.routes.orElse(appRoutes.routes) } + +object ApiRouter { + object TaxOfficeNumber { + val regex = "(\\d{3})".r + def unapply(string: String): Option[String] = { + string match { + case regex(result) => Some(result) + case _ => None + } + } + } + + object TaxOfficeReference { + val regex = "([0-9A-Z]{1,10})".r + def unapply(string: String): Option[String] = { + string match { + case regex(result) => Some(result) + case _ => None + } + } + } +} \ No newline at end of file diff --git a/resources/public/api/conf/1.0/application.raml b/resources/public/api/conf/1.0/application.raml index 4df38c3..5e417b0 100644 --- a/resources/public/api/conf/1.0/application.raml +++ b/resources/public/api/conf/1.0/application.raml @@ -44,7 +44,7 @@ types: get: is: [ headers.acceptHeader ] displayName: Get access to ePAYE data - description: This resource is the entry point to the ePAYE API. It will return a set of URLs, allowing you to access ePAYE data for each account. This will include the employer's ePAYE balance, monthly totals of RTI charges, non-RTI charges and a breakdown of individual charges. + description: This resource is the entry point to the ePAYE API. It will return a set of endpoints, allowing you to access ePAYE data for each account. This will include the employer's PAYE balance, monthly totals of RTI charges, non-RTI charges and a breakdown of individual charges. (annotations.scope): "read:epaye" securedBy: [ sec.oauth_2_0: { scopes: [ "read:epaye" ] } ] responses: @@ -110,7 +110,7 @@ types: type: string example: "A000001" taxYear: - description: The tax year... + description: The tax year type: string example: "2017-18" get: diff --git a/resources/public/api/conf/1.0/documentation/overview.md b/resources/public/api/conf/1.0/documentation/overview.md index dc2117d..8bdeeea 100644 --- a/resources/public/api/conf/1.0/documentation/overview.md +++ b/resources/public/api/conf/1.0/documentation/overview.md @@ -7,4 +7,4 @@ This API retrieves data about [Pay As You Earn for employers (ePAYE)](https://ww This information is intended for use by employers and their agents to check their own calculations of their clients' tax liability. -This version of the API is currently in development and is very likely to be enhanced. + diff --git a/resources/public/api/conf/1.0/schemas/Definitions.schema.json b/resources/public/api/conf/1.0/schemas/Definitions.schema.json index 742ef2b..2241cce 100644 --- a/resources/public/api/conf/1.0/schemas/Definitions.schema.json +++ b/resources/public/api/conf/1.0/schemas/Definitions.schema.json @@ -18,11 +18,15 @@ }, "firstDay": { "description": "The first day of the tax year", - "$ref": "#/definitions/date" + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "example": "2016-04-06" }, "lastDay": { "description": "The last day of the tax year", - "$ref": "#/definitions/date" + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "example": "2017-04-05" } }, "required": [ @@ -44,11 +48,15 @@ }, "firstDay": { "description": "The first day of the tax month", - "$ref": "#/definitions/date" + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "example": "2017-05-06" }, "lastDay": { "description": "The last day of the tax month", - "$ref": "#/definitions/date" + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "example": "2017-06-05" } }, "required": [ @@ -70,12 +78,12 @@ "taxOfficeNumber": { "description": "A tax office number.", "type": "string", - "pattern": "^\\w{3}$" + "pattern": "^\\d{3}$" }, "taxOfficeReference": { "description": "A tax office reference.", "type": "string", - "pattern": "^\\w{7,10}$" + "pattern": "^[0-9A-Z]{1,10}$" }, "empRef": { "type": "string", diff --git a/test/uk/gov/hmrc/epayeapi/router/ApiRouterTest.scala b/test/uk/gov/hmrc/epayeapi/router/ApiRouterTest.scala new file mode 100644 index 0000000..625ace8 --- /dev/null +++ b/test/uk/gov/hmrc/epayeapi/router/ApiRouterTest.scala @@ -0,0 +1,88 @@ +/* + * Copyright 2018 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.epayeapi.router + + +import uk.gov.hmrc.epayeapi.router.ApiRouter.{TaxOfficeNumber, TaxOfficeReference} +import uk.gov.hmrc.play.test.UnitSpec + +import scala.util.Random + +class ApiRouterSpec extends UnitSpec { + "ApiRouter.TaxOfficeNumber" should { + "extract any 3 digit value" in { + for(i <- 100 to 999) + TaxOfficeNumber.unapply(i.toString) shouldBe Some(i.toString) + } + + "not extract any 2 digit value" in { + for(i <- 10 to 99) + TaxOfficeNumber.unapply(i.toString) shouldBe None + } + + "not extract any 1 digit value" in { + for(i <- 1 to 9) + TaxOfficeNumber.unapply(i.toString) shouldBe None + } + + "not extract any 4 digit or more value" in { + for(_ <- 1 to 100) { + val i = Random.nextInt(10) + + TaxOfficeNumber.unapply((i * 1000).toString) shouldBe None + } + } + + "not extract any character values" in { + TaxOfficeNumber.unapply("AAA") shouldBe None + } + + "not extract any mixed values" in { + TaxOfficeNumber.unapply("AA1") shouldBe None + } + } + + "ApiRouter.TaxOfficeReference" should { + "extract any digit value shorter than 10 characters" in { + for (length <- 1 to 10) + TaxOfficeReference.unapply("1" * length) shouldBe Some("1" * length) + } + + "not extract any digit value longer than 10 characters" in { + for (length <- 11 to 30) + TaxOfficeReference.unapply("1" * length) shouldBe None + } + + "extract any uppercase character value shorter than 10 characters" in { + for (length <- 1 to 10) + TaxOfficeReference.unapply("A" * length) shouldBe Some("A" * length) + } + + "not extract any uppercase character value longer than 10 characters" in { + for (length <- 11 to 30) + TaxOfficeReference.unapply("A" * length) shouldBe None + } + + "not extract any lowercase characters" in { + for (length <- 1 to 30) + TaxOfficeReference.unapply("a" * length) shouldBe None + } + } + + +}