From 372c68ba23d939cb1d35de50a1717d8d5ec1ec69 Mon Sep 17 00:00:00 2001 From: Tim Golding Date: Mon, 12 Feb 2018 10:32:43 +0000 Subject: [PATCH] YTA-3207: Added /statements endpoint. --- .../epayeapi/connectors/EpayeConnector.scala | 10 ++ .../controllers/GetStatementsController.scala | 60 +++++++ app/uk/gov/hmrc/epayeapi/models/TaxYear.scala | 5 + .../epayeapi/models/in/EpayeMasterData.scala | 24 +++ .../hmrc/epayeapi/models/in/EpayeReads.scala | 2 + .../models/out/AnnualStatementJson.scala | 9 +- .../hmrc/epayeapi/models/out/JsonWrites.scala | 6 + .../epayeapi/models/out/StatementsJson.scala | 73 +++++++++ .../gov/hmrc/epayeapi/router/ApiRouter.scala | 12 +- .../public/api/conf/1.0/application.raml | 34 ++++ .../1.0/examples/AnnualStatement.get.json | 10 +- .../api/conf/1.0/examples/Statements.get.json | 46 ++++++ .../conf/1.0/schemas/Definitions.schema.json | 3 +- .../conf/1.0/schemas/Statements.schema.json | 76 +++++++++ test/common/Fixtures.scala | 62 +++++++ test/common/RestAssertions.scala | 17 +- test/contract/GetStatementsSpec.scala | 59 +++++++ test/integration/GetStatementsSpec.scala | 152 ++++++++++++++++++ .../models/out/StatementsJsonSpec.scala | 72 +++++++++ .../hmrc/epayeapi/util/TestTimeMachine.scala | 31 ++++ 20 files changed, 750 insertions(+), 13 deletions(-) create mode 100644 app/uk/gov/hmrc/epayeapi/controllers/GetStatementsController.scala create mode 100644 app/uk/gov/hmrc/epayeapi/models/in/EpayeMasterData.scala create mode 100644 app/uk/gov/hmrc/epayeapi/models/out/StatementsJson.scala create mode 100644 resources/public/api/conf/1.0/examples/Statements.get.json create mode 100644 resources/public/api/conf/1.0/schemas/Statements.schema.json create mode 100644 test/contract/GetStatementsSpec.scala create mode 100644 test/integration/GetStatementsSpec.scala create mode 100644 test/uk/gov/hmrc/epayeapi/models/out/StatementsJsonSpec.scala create mode 100644 test/uk/gov/hmrc/epayeapi/util/TestTimeMachine.scala diff --git a/app/uk/gov/hmrc/epayeapi/connectors/EpayeConnector.scala b/app/uk/gov/hmrc/epayeapi/connectors/EpayeConnector.scala index 3d322d2..754c763 100644 --- a/app/uk/gov/hmrc/epayeapi/connectors/EpayeConnector.scala +++ b/app/uk/gov/hmrc/epayeapi/connectors/EpayeConnector.scala @@ -76,5 +76,15 @@ case class EpayeConnector @Inject() ( get[EpayeMonthlyStatement](url, headers) } + + def getMasterData(empRef: EmpRef, headers: HeaderCarrier): Future[EpayeResponse[EpayeMasterData]] = { + val url = + s"${config.epayeBaseUrl}" + + s"/epaye" + + s"/${empRef.encodedValue}" + + s"/api/v1/master-data" + + get[EpayeMasterData](url, headers) + } } diff --git a/app/uk/gov/hmrc/epayeapi/controllers/GetStatementsController.scala b/app/uk/gov/hmrc/epayeapi/controllers/GetStatementsController.scala new file mode 100644 index 0000000..337d885 --- /dev/null +++ b/app/uk/gov/hmrc/epayeapi/controllers/GetStatementsController.scala @@ -0,0 +1,60 @@ +/* + * 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 javax.inject.{Inject, Singleton} + +import akka.stream.Materializer +import play.api.libs.json.Json +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.statementsJsonWrites +import uk.gov.hmrc.epayeapi.models.in.{EpayeMasterData, EpayeResponse, EpayeSuccess} +import uk.gov.hmrc.epayeapi.models.out.{EmpRefsJson, StatementsJson} + +import scala.concurrent.ExecutionContext + +@Singleton +case class GetStatementsController @Inject()( + config: EpayeApiConfig, + authConnector: AuthConnector, + epayeConnector: EpayeConnector, + implicit val ec: ExecutionContext, + implicit val mat: Materializer +) + extends ApiController + with EpayeErrorHandler { + + def getStatements(empRef: EmpRef): EssentialAction = { + EmpRefAction(empRef) { + Action.async { implicit request => + val masterData = epayeConnector.getMasterData(empRef, hc) + masterData.map { + successHandler(empRef) orElse errorHandler + } + } + } + } + + def successHandler(empRef: EmpRef): PartialFunction[EpayeResponse[EpayeMasterData], Result] = { + case EpayeSuccess(EpayeMasterData(_, yearRegistered)) => + val statements = StatementsJson(config.apiBaseUrl, empRef, yearRegistered) + Ok(Json.toJson(statements)) + } +} diff --git a/app/uk/gov/hmrc/epayeapi/models/TaxYear.scala b/app/uk/gov/hmrc/epayeapi/models/TaxYear.scala index 303a5d9..2b82389 100644 --- a/app/uk/gov/hmrc/epayeapi/models/TaxYear.scala +++ b/app/uk/gov/hmrc/epayeapi/models/TaxYear.scala @@ -40,6 +40,11 @@ case class TaxYear( firstDay.plusYears(1).minusDays(1) } +object TaxYear { + def apply(date: LocalDate): TaxYear = + TaxYear(TaxYearResolver.taxYearFor(date)) +} + object ExtractTaxYear { private lazy val pattern = """20(\d\d)-(\d\d)""".r diff --git a/app/uk/gov/hmrc/epayeapi/models/in/EpayeMasterData.scala b/app/uk/gov/hmrc/epayeapi/models/in/EpayeMasterData.scala new file mode 100644 index 0000000..7ce6bb9 --- /dev/null +++ b/app/uk/gov/hmrc/epayeapi/models/in/EpayeMasterData.scala @@ -0,0 +1,24 @@ +/* + * 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.models.in + +import uk.gov.hmrc.epayeapi.models.TaxYear + +case class EpayeMasterData( + accountsOfficeReference: Option[String], + yearRegistered: Option[TaxYear] +) diff --git a/app/uk/gov/hmrc/epayeapi/models/in/EpayeReads.scala b/app/uk/gov/hmrc/epayeapi/models/in/EpayeReads.scala index 47c1a88..b149755 100644 --- a/app/uk/gov/hmrc/epayeapi/models/in/EpayeReads.scala +++ b/app/uk/gov/hmrc/epayeapi/models/in/EpayeReads.scala @@ -51,5 +51,7 @@ trait EpayeReads { implicit lazy val epayeMonthlyPaymentItemReads: Reads[EpayeMonthlyPaymentItem] = reads[EpayeMonthlyPaymentItem] implicit lazy val epayeMonthlyBalanceReads: Reads[EpayeMonthlyBalance] = reads[EpayeMonthlyBalance] + implicit lazy val epayeMasterDataResponse: Reads[EpayeMasterData] = reads[EpayeMasterData] + implicit lazy val epayeEmpRefsResponse: Format[EpayeEmpRefsResponse] = Json.format[EpayeEmpRefsResponse] } diff --git a/app/uk/gov/hmrc/epayeapi/models/out/AnnualStatementJson.scala b/app/uk/gov/hmrc/epayeapi/models/out/AnnualStatementJson.scala index a0ead45..bc7867a 100644 --- a/app/uk/gov/hmrc/epayeapi/models/out/AnnualStatementJson.scala +++ b/app/uk/gov/hmrc/epayeapi/models/out/AnnualStatementJson.scala @@ -149,4 +149,11 @@ object AnnualStatementJson { ) ) -} \ No newline at end of file +} + +case class AllAnnualStatementLinksJson( + empRefs: Link, + self: Link, + next: Link, + previous: Link +) \ No newline at end of file diff --git a/app/uk/gov/hmrc/epayeapi/models/out/JsonWrites.scala b/app/uk/gov/hmrc/epayeapi/models/out/JsonWrites.scala index 6566d36..bc07422 100644 --- a/app/uk/gov/hmrc/epayeapi/models/out/JsonWrites.scala +++ b/app/uk/gov/hmrc/epayeapi/models/out/JsonWrites.scala @@ -68,4 +68,10 @@ trait JsonWrites { implicit lazy val paymentJsonWrites: Writes[PaymentJson] = writes[PaymentJson] implicit lazy val monthlySummaryJsonWrites: Writes[MonthlySummaryJson] = writes[MonthlySummaryJson] implicit lazy val monthlyStatementLinksJsonWrites: Writes[MonthlyStatementLinksJson] = writes[MonthlyStatementLinksJson] + + implicit lazy val statementsJsonWrites: Writes[StatementsJson] = writes[StatementsJson] + implicit lazy val statementsEmbeddedJsonWrites: Writes[Embedded] = writes[Embedded] + implicit lazy val statementsStatementJsonWrites: Writes[Statement] = writes[Statement] + implicit lazy val statementsStatementLinksJsonWrites: Writes[Links] = writes[Links] + implicit lazy val statementsLinksJsonWrites: Writes[StatementLinks] = writes[StatementLinks] } diff --git a/app/uk/gov/hmrc/epayeapi/models/out/StatementsJson.scala b/app/uk/gov/hmrc/epayeapi/models/out/StatementsJson.scala new file mode 100644 index 0000000..5e10538 --- /dev/null +++ b/app/uk/gov/hmrc/epayeapi/models/out/StatementsJson.scala @@ -0,0 +1,73 @@ +/* + * 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.models.out + +import org.joda.time.LocalDate +import uk.gov.hmrc.domain.EmpRef +import uk.gov.hmrc.epayeapi.models.TaxYear + +case class StatementsJson( + _embedded: Embedded, + _links: Links +) + +case class Embedded( + statements: Seq[Statement] +) + +case class Statement( + taxYear: TaxYear, + _links: StatementLinks +) + +case class StatementLinks( + self: Link +) + +case class Links( + empRefs: Link, + self: Link +) + +object StatementsJson { + def apply(apiBaseUrl: String, empRef: EmpRef, taxYearOfRegistration: Option[TaxYear]): StatementsJson = { + def taxYearStatement(taxYear: TaxYear): Statement = { + Statement( + taxYear = taxYear, + _links = StatementLinks(self = Link.anualStatementLink(apiBaseUrl, empRef, taxYear)) + ) + } + + val currentTaxYear = TaxYear(LocalDate.now()) + + val statements: Seq[Statement] = for { + regYear <- taxYearOfRegistration.toSeq + taxYearFrom <- (regYear.yearFrom to currentTaxYear.yearFrom) + taxYear = TaxYear(taxYearFrom) + } yield taxYearStatement(taxYear) + + val links = Links( + empRefs = Link.empRefsLink(apiBaseUrl), + self = Link.statementsLink(apiBaseUrl, empRef) + ) + + StatementsJson( + _embedded = Embedded(statements), + _links = links + ) + } +} \ No newline at end of file diff --git a/app/uk/gov/hmrc/epayeapi/router/ApiRouter.scala b/app/uk/gov/hmrc/epayeapi/router/ApiRouter.scala index 587b9f6..88301c2 100644 --- a/app/uk/gov/hmrc/epayeapi/router/ApiRouter.scala +++ b/app/uk/gov/hmrc/epayeapi/router/ApiRouter.scala @@ -22,16 +22,17 @@ import play.api.routing.Router.Routes import play.api.routing.sird._ import play.api.routing.{Router, SimpleRouter} import uk.gov.hmrc.domain.EmpRef -import uk.gov.hmrc.epayeapi.controllers.{GetAnnualStatementController, GetEmpRefsController, GetMonthlyStatementController, GetSummaryController} +import uk.gov.hmrc.epayeapi.controllers._ import uk.gov.hmrc.epayeapi.models.{ExtractTaxYear, TaxMonth} @Singleton case class ApiRouter @Inject() ( prodRoutes: prod.Routes, getEmpRefsController: GetEmpRefsController, - getTotalsController: GetSummaryController, + getSummaryController: GetSummaryController, getAnnualStatementController: GetAnnualStatementController, - getMonthlyStatementController: GetMonthlyStatementController + getMonthlyStatementController: GetMonthlyStatementController, + getStatementsController: GetStatementsController ) extends SimpleRouter { val appRoutes = Router.from { @@ -39,7 +40,10 @@ case class ApiRouter @Inject() ( getEmpRefsController.getEmpRefs() case GET(p"/${TaxOfficeNumber(ton)}/${TaxOfficeReference(tor)}") => - getTotalsController.getSummary(EmpRef(ton, tor)) + getSummaryController.getSummary(EmpRef(ton, tor)) + + case GET(p"/${TaxOfficeNumber(ton)}/${TaxOfficeReference(tor)}/statements") => + getStatementsController.getStatements(EmpRef(ton, tor)) case GET(p"/${TaxOfficeNumber(ton)}/${TaxOfficeReference(tor)}/statements/${ ExtractTaxYear(taxYear) }") => getAnnualStatementController.getAnnualStatement(EmpRef(ton, tor), taxYear) diff --git a/resources/public/api/conf/1.0/application.raml b/resources/public/api/conf/1.0/application.raml index 5e417b0..3d334db 100644 --- a/resources/public/api/conf/1.0/application.raml +++ b/resources/public/api/conf/1.0/application.raml @@ -99,6 +99,40 @@ types: "code" : "INVALID_EMPREF", "message" : "Provided EmpRef is not associated with your account" } + /{taxOfficeNumber}/{taxOfficeReference}/statements: + uriParameters: + taxOfficeNumber: + description: A unique identifier made up of tax office number + type: string + example: "001" + taxOfficeReference: + description: A unique identifier made up of the tax office reference + type: string + example: "A000001" + get: + is: [ headers.acceptHeader ] + displayName: Get links to all Annual Statements + description: This resource returns links to all available Annual Statements + (annotations.scope): "read:epaye" + securedBy: [ sec.oauth_2_0: { scopes: [ "read:epaye" ] } ] + responses: + 200: + body: + application/json: + type: !include schemas/Statements.schema.json + example: !include examples/Statements.get.json + 403: + body: + application/json: + type: !include schemas/ErrorCodes.schema.json + examples: + notOpenStatus: + description: You don't currently have an ePAYE enrolment on this account. + value: | + { + "code" : "INVALID_EMPREF", + "message" : "Provided EmpRef is not associated with your account" + } /{taxOfficeNumber}/{taxOfficeReference}/statements/{taxYear}: uriParameters: taxOfficeNumber: diff --git a/resources/public/api/conf/1.0/examples/AnnualStatement.get.json b/resources/public/api/conf/1.0/examples/AnnualStatement.get.json index a1c7dda..06e0e72 100644 --- a/resources/public/api/conf/1.0/examples/AnnualStatement.get.json +++ b/resources/public/api/conf/1.0/examples/AnnualStatement.get.json @@ -40,7 +40,7 @@ "isSpecified": false, "_links": { "self": { - "href": "https://api.service.hmrc.gov.uk/organisations/paye/840/GZ00064/statements/2016-17/4" + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements/2016-17/4" } } } @@ -51,16 +51,16 @@ "href": "https://api.service.hmrc.gov.uk/organisations/paye/" }, "statements": { - "href": "https://api.service.hmrc.gov.uk/organisations/paye/840/GZ00064/statements" + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements" }, "self": { - "href": "https://api.service.hmrc.gov.uk/organisations/paye/840/GZ00064/statements/2016-17" + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements/2016-17" }, "next": { - "href": "https://api.service.hmrc.gov.uk/organisations/paye/840/GZ00064/statements/2017-18" + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements/2017-18" }, "previous": { - "href": "https://api.service.hmrc.gov.uk/organisations/paye/840/GZ00064/statements/2015-16" + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements/2015-16" } } } \ No newline at end of file diff --git a/resources/public/api/conf/1.0/examples/Statements.get.json b/resources/public/api/conf/1.0/examples/Statements.get.json new file mode 100644 index 0000000..c6efd4b --- /dev/null +++ b/resources/public/api/conf/1.0/examples/Statements.get.json @@ -0,0 +1,46 @@ +{ + "_embedded": { + "statements": [{ + "taxYear": { + "year": "2016-17", + "firstDay": "2016-04-06", + "lastDay": "2017-04-05" + }, + "_links": { + "self": { + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements/2016-17" + } + } + }, { + "taxYear": { + "year": "2015-16", + "firstDay": "2015-04-06", + "lastDay": "2016-04-05" + }, + "_links": { + "self": { + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements/2015-16" + } + } + }, { + "taxYear": { + "year": "2014-15", + "firstDay": "2014-04-06", + "lastDay": "2015-04-05" + }, + "_links": { + "self": { + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements/2014-15" + } + } + }] + }, + "_links": { + "empRefs": { + "href": "https://api.service.hmrc.gov.uk/organisations/paye/" + }, + "self": { + "href": "https://api.service.hmrc.gov.uk/organisations/paye/001/AB00001/statements" + } + } +} \ No newline at end of file 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 2241cce..c32bae7 100644 --- a/resources/public/api/conf/1.0/schemas/Definitions.schema.json +++ b/resources/public/api/conf/1.0/schemas/Definitions.schema.json @@ -14,7 +14,8 @@ "year": { "type": "string", "description": "The tax year", - "pattern": "^\\d{4}-\\d{2}$" + "pattern": "^\\d{4}-\\d{2}$", + "example": "2016-17" }, "firstDay": { "description": "The first day of the tax year", diff --git a/resources/public/api/conf/1.0/schemas/Statements.schema.json b/resources/public/api/conf/1.0/schemas/Statements.schema.json new file mode 100644 index 0000000..3aec162 --- /dev/null +++ b/resources/public/api/conf/1.0/schemas/Statements.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Statements links for the given empRef", + "properties": { + "_embedded": { + "$ref": "#/definitions/embedded" + }, + "_links": { + "$ref": "#/definitions/links" + } + }, + "required": [ + "_embedded", + "_links" + ], + "definitions": { + "embedded": { + "type": "object", + "properties": { + "statements": { + "$ref": "#/definitions/embeddedStatements" + } + }, + "required": [ + "statements" + ] + }, + "embeddedStatements": { + "type": "array", + "description": "Annual statement links for each registered tax year", + "items": { + "type": "object", + "properties": { + "taxYear": { + "$ref": "Definitions.schema.json#/definitions/taxYear" + }, + "_links": { + "$ref": "#/definitions/annualStatementLinks" + } + }, + "required": [ + "taxYear", + "_links" + ] + } + }, + "annualStatementLinks": { + "type": "object", + "description": "Links related to this annual statement", + "properties": { + "self": { + "$ref": "Definitions.schema.json#/definitions/link" + } + }, + "required": [ + "self" + ] + }, + "links": { + "type": "object", + "properties": { + "empRefs": { + "$ref": "Definitions.schema.json#/definitions/link" + }, + "self": { + "$ref": "Definitions.schema.json#/definitions/link" + } + }, + "required": [ + "empRefs", + "self" + ] + } + } +} \ No newline at end of file diff --git a/test/common/Fixtures.scala b/test/common/Fixtures.scala index 99709b5..4a5e0ea 100644 --- a/test/common/Fixtures.scala +++ b/test/common/Fixtures.scala @@ -18,9 +18,20 @@ package common import play.api.libs.json.{JsValue, Json} import uk.gov.hmrc.domain.EmpRef +import uk.gov.hmrc.epayeapi.models.TaxYear object Fixtures { + def epayeMasterData(empRef: EmpRef, taxYear: TaxYear): String = + s""" + |{ + | "accountsOfficeReference": "${empRef.toString}", + | "yearRegistered": { + | "yearFrom": ${taxYear.yearFrom} + | } + |} + """.stripMargin + val epayeAnnualStatement: String = """ |{ @@ -198,6 +209,57 @@ object Fixtures { """.stripMargin ) + def expectedStatementLinksJson(apiBaseUrl: String, empRef: EmpRef): JsValue = Json.parse( + s""" + |{ + | "_embedded": { + | "statements": [{ + | "taxYear": { + | "year": "2016-17", + | "firstDay": "2016-04-06", + | "lastDay": "2017-04-05" + | }, + | "_links": { + | "self": { + | "href": "$apiBaseUrl/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements/2016-17" + | } + | } + | }, { + | "taxYear": { + | "year": "2015-16", + | "firstDay": "2015-04-06", + | "lastDay": "2016-04-05" + | }, + | "_links": { + | "self": { + | "href": "$apiBaseUrl/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements/2015-16" + | } + | } + | }, { + | "taxYear": { + | "year": "2014-15", + | "firstDay": "2014-04-06", + | "lastDay": "2015-04-05" + | }, + | "_links": { + | "self": { + | "href": "$apiBaseUrl/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements/2014-15" + | } + | } + | }] + | }, + | "_links": { + | "empRefs": { + | "href": "$apiBaseUrl/organisations/paye/" + | }, + | "self": { + | "href": "$apiBaseUrl/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements" + | } + | } + |} + """.stripMargin + ) + def authorisedEnrolmentJson(empRef: EmpRef): String = { s""" |{ diff --git a/test/common/RestAssertions.scala b/test/common/RestAssertions.scala index 61af954..0831f00 100644 --- a/test/common/RestAssertions.scala +++ b/test/common/RestAssertions.scala @@ -99,11 +99,24 @@ class ClientGivens extends BaseClientGivens[ClientGivens] { } this } - - } class ClientWithEmpRefGivens(empRef: EmpRef) extends BaseClientGivens[ClientWithEmpRefGivens] { + def epayeMasterDataReturns(body: String, status: Int = 200): ClientWithEmpRefGivens = { + stubFor( + get( + urlPathEqualTo(s"/epaye/${empRef.encodedValue}/api/v1/master-data") + ).willReturn( + aResponse() + .withBody(body) + .withHeader("Content-Type", "application/json") + .withStatus(status) + ) + ) + + this + } + def epayeTotalsReturns(body: String): ClientWithEmpRefGivens = { stubFor( get( diff --git a/test/contract/GetStatementsSpec.scala b/test/contract/GetStatementsSpec.scala new file mode 100644 index 0000000..a4f1acc --- /dev/null +++ b/test/contract/GetStatementsSpec.scala @@ -0,0 +1,59 @@ +/* + * 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 contract + +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.routing.Router +import uk.gov.hmrc.epayeapi.models.TaxYear +import uk.gov.hmrc.epayeapi.router.RoutesProvider + +class GetStatementsSpec + 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() + + "/organisations/epaye/{ton}/{tor}/statements" should { + + "returns a response body that conforms with the Statements schema" in { + val empRef = randomEmpRef() + val taxYear = TaxYear(2017) + + val statementsUrl = + s"$baseUrl/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements" + + val statementsSchemaPath = s"${app.path.toURI}/resources/public/api/conf/1.0/schemas/Statements.schema.json" + + given() + .clientWith(empRef).isAuthorized + .and().epayeMasterDataReturns(Fixtures.epayeMasterData(empRef, taxYear)) + .when + .get(statementsUrl).withAuthHeader() + .thenAssertThat() + .bodyIsOfSchema(statementsSchemaPath) + } + } +} diff --git a/test/integration/GetStatementsSpec.scala b/test/integration/GetStatementsSpec.scala new file mode 100644 index 0000000..6004f15 --- /dev/null +++ b/test/integration/GetStatementsSpec.scala @@ -0,0 +1,152 @@ +/* + * 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.TaxYear +import uk.gov.hmrc.epayeapi.models.out.ApiErrorJson +import uk.gov.hmrc.epayeapi.util.TestTimeMachine.withFixedLocalDate + +class GetStatementsSpec extends IntegrationTestBase { + + "statements" should { + val regYear = TaxYear(2014) + val currentTaxYear = TaxYear(2016) + + "return 200 OK with annual statement links" in new Setup { withFixedLocalDate(currentTaxYear.firstDay) { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeMasterDataReturns( + s""" + |{ + | "accountsOfficeReference": "${empRef.taxOfficeReference}", + | "yearRegistered": { + | "yearFrom": ${regYear.yearFrom} + | } + |} + """.stripMargin + ) + .when() + .get(url) + .thenAssertThat() + .statusCodeIs(200) + .bodyIsOfJson(Json.parse( + s""" + |{ + | "_embedded": { + | "statements": [{ + | "taxYear": { + | "year": "2014-15", + | "firstDay": "2014-04-06", + | "lastDay": "2015-04-05" + | }, + | "_links": { + | "self": { + | "href": "https://api.service.hmrc.gov.uk/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements/2014-15" + | } + | } + | }, { + | "taxYear": { + | "year": "2015-16", + | "firstDay": "2015-04-06", + | "lastDay": "2016-04-05" + | }, + | "_links": { + | "self": { + | "href": "https://api.service.hmrc.gov.uk/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements/2015-16" + | } + | } + | }, { + | "taxYear": { + | "year": "2016-17", + | "firstDay": "2016-04-06", + | "lastDay": "2017-04-05" + | }, + | "_links": { + | "self": { + | "href": "https://api.service.hmrc.gov.uk/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements/2016-17" + | } + | } + | }] + | }, + | "_links": { + | "empRefs": { + | "href": "https://api.service.hmrc.gov.uk/organisations/paye/" + | }, + | "self": { + | "href": "https://api.service.hmrc.gov.uk/organisations/paye/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements" + | } + | } + |} + """.stripMargin + )) + }} + + "return 500 Internal Server Error if upstream returns invalid JSON" in new Setup { + given() + .clientWith(empRef).isAuthorized + .and() + .epayeMasterDataReturns("{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() + .epayeMasterDataReturns("", 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() + .epayeMasterDataReturns("", 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 apiBaseUrl = app.configuration.underlying.getString("api.baseUrl") + val url = s"$baseUrl/${empRef.taxOfficeNumber}/${empRef.taxOfficeReference}/statements" + } +} diff --git a/test/uk/gov/hmrc/epayeapi/models/out/StatementsJsonSpec.scala b/test/uk/gov/hmrc/epayeapi/models/out/StatementsJsonSpec.scala new file mode 100644 index 0000000..a49a7f2 --- /dev/null +++ b/test/uk/gov/hmrc/epayeapi/models/out/StatementsJsonSpec.scala @@ -0,0 +1,72 @@ +/* + * 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.models.out + +import common.EmpRefGenerator +import org.joda.time.LocalDate +import org.scalatest.{Matchers, WordSpec} +import uk.gov.hmrc.domain.EmpRef +import uk.gov.hmrc.epayeapi.models.TaxYear +import uk.gov.hmrc.epayeapi.models.out.Link.prefix +import uk.gov.hmrc.epayeapi.util.TestTimeMachine.withFixedLocalDate + +class StatementsJsonSpec extends WordSpec with Matchers { + val apiBaseUrl = "[API_BASE_URL]" + val empRef = EmpRefGenerator.getEmpRef + + val _links = Links( + empRefs = Link.empRefsLink(apiBaseUrl), + self = Link.statementsLink(apiBaseUrl, empRef) + ) + + "StatementsJson.apply" should { + "return a result with empty statements when the year of registration is missing" in { + StatementsJson.apply(apiBaseUrl, empRef, None) shouldBe StatementsJson(Embedded(Seq.empty), _links) + } + "return a result with a statement link for the current tax year" in { + val currentTaxYear = TaxYear(LocalDate.now()) + + val currentTaxYearStatement = Statement( + currentTaxYear, + StatementLinks(annualStatementLink(currentTaxYear)) + ) + val _embedded = Embedded(Seq(currentTaxYearStatement)) + + StatementsJson.apply(apiBaseUrl, empRef, Some(currentTaxYear)) shouldBe StatementsJson(_embedded, _links) + } + "return a result with statement links for 2015, 2016 and 2017" in { + val yearOfRegistration = 2015 + val currentTaxYearFrom = 2017 + + val today = new LocalDate(currentTaxYearFrom, 6, 6) + + withFixedLocalDate(today) { + val statements = for { + taxYearFrom <- yearOfRegistration to currentTaxYearFrom + taxYear = TaxYear(taxYearFrom) + } yield Statement(taxYear, StatementLinks(annualStatementLink(taxYear))) + + val _embedded = Embedded(statements) + + StatementsJson.apply(apiBaseUrl, empRef, Some(TaxYear(yearOfRegistration))) shouldBe StatementsJson(_embedded, _links) + } + } + } + + private def annualStatementLink(taxYear: TaxYear, baseUrl: String = apiBaseUrl, eRef: EmpRef = empRef): Link = + Link(s"$baseUrl$prefix/${eRef.taxOfficeNumber}/${eRef.taxOfficeReference}/statements/${taxYear.asString}") +} diff --git a/test/uk/gov/hmrc/epayeapi/util/TestTimeMachine.scala b/test/uk/gov/hmrc/epayeapi/util/TestTimeMachine.scala new file mode 100644 index 0000000..90f94e2 --- /dev/null +++ b/test/uk/gov/hmrc/epayeapi/util/TestTimeMachine.scala @@ -0,0 +1,31 @@ +/* + * 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.util + +import org.joda.time.{DateTimeUtils, LocalDate} + +object TestTimeMachine { + def withFixedLocalDate(date: LocalDate)(exec: => Unit): Unit = { + DateTimeUtils.setCurrentMillisFixed(date.toDate.getTime) + try { + exec + } + finally { + DateTimeUtils.setCurrentMillisSystem() + } + } +}