diff --git a/README.md b/README.md index 2afdf9b..e33b1bf 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://travis-ci.org/hmrc/openid-connect-userinfo.svg?branch=master)](https://travis-ci.org/hmrc/openid-connect-userinfo) [ ![Download](https://api.bintray.com/packages/hmrc/releases/openid-connect-userinfo/images/download.svg) ](https://bintray.com/hmrc/releases/openid-connect-userinfo/_latestVersion) -This Beta REST API aims to provide a specification compliant OpenID Connect implementation. It allows consumers to access user details with consent and in the OpenID Connect UserInfo format. +The REST API, exposed by the HMRC API Platform as /userinfo to external clients, aims to provide a specification compliant OpenID Connect implementation. It allows consumers to access user details with consent and in the OpenID Connect UserInfo format. A typical workflow would be: @@ -15,10 +15,15 @@ User details data structures follow the OpenId Connect UserInfo specification (s You can dive deeper into the documentation in the [API Developer Hub](https://developer.service.hmrc.gov.uk/api-documentation/docs/api#openid-connect-userinfo). -## Deprecated Class Usage -You can find the deprecation suppression `@nowarn("cat=deprecation")` in the code in few places. +## Authentication tokens +Note, the /userinfo endpoint is an external API endpoint. This endpoint requires an API token for authentication. -The reason is that when the latest `v2.Retrievals` was used then the integration tests were broken and wasn't obvious why. +## API + +| Method | HMRC API Platform Path | Internal Path | Description | +|--------|------------------------|---------------|----------------------------------------------------------------------------------------------------------------------| +| GET | /userinfo | / | Returns information about an End-User as requested in the openid scopes as documented in the published API document. | +| POST | Internal use only | / | | ## Running Locally Run the service `sbt run -Drun.mode=Dev` diff --git a/app/config/FeatureSwitch.scala b/app/config/FeatureSwitch.scala deleted file mode 100644 index 227c326..0000000 --- a/app/config/FeatureSwitch.scala +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024 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 config - -case class FeatureSwitch(name: String, isEnabled: Boolean) - -object FeatureSwitch { - - def forName(name: String) = FeatureSwitch(name, java.lang.Boolean.getBoolean(systemPropertyName(name))) - - def enable(switch: FeatureSwitch): FeatureSwitch = setProp(switch.name, true) - - def disable(switch: FeatureSwitch): FeatureSwitch = setProp(switch.name, false) - - private def setProp(name: String, value: Boolean): FeatureSwitch = { - sys.props += ((systemPropertyName(name), value.toString)) - forName(name) - } - - private def systemPropertyName(name: String) = s"feature.$name" - -} - -object UserInfoFeatureSwitches { - - def countryCode = FeatureSwitch.forName("countryCode") - def addressLine5 = FeatureSwitch.forName("addressLine5") - - def allSwitches: Seq[FeatureSwitch] = Seq(countryCode, addressLine5) - -} diff --git a/app/controllers/testOnly/FeatureSwitchController.scala b/app/controllers/testOnly/FeatureSwitchController.scala deleted file mode 100644 index 641903c..0000000 --- a/app/controllers/testOnly/FeatureSwitchController.scala +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 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 controllers.testOnly - -import config.{FeatureSwitch, UserInfoFeatureSwitches} -import play.api.libs.json.{JsValue, Json, OWrites, Reads} -import play.api.mvc.{Action, AnyContent, ControllerComponents} -import uk.gov.hmrc.play.bootstrap.backend.controller.BackendController - -import javax.inject.{Inject, Singleton} -import scala.concurrent.{ExecutionContext, Future} - -@Singleton -class FeatureSwitchController @Inject() ()(implicit cc: ControllerComponents, ec: ExecutionContext) extends BackendController(cc) { - - implicit val featureSwitchReads: Reads[FeatureSwitch] = Json.reads[FeatureSwitch] - implicit val featureSwitchWrites: OWrites[FeatureSwitch] = Json.writes[FeatureSwitch] - implicit val FeatureSwitchRequestReads: Reads[FeatureSwitchRequest] = Json.reads[FeatureSwitchRequest] - - def getFlags: Action[AnyContent] = Action { - Ok(currentFeatureSwitchesAsJson) - } - - def setFlags(): Action[JsValue] = { - Action.async(parse.json) { implicit request => - withJsonBody[FeatureSwitchRequest] { ffRequest => - val featureSwitches: Seq[FeatureSwitch] = ffRequest.featureSwitches - featureSwitches.foreach(fs => - if (fs.isEnabled) { - FeatureSwitch.enable(FeatureSwitch.forName(fs.name)) - } else { - FeatureSwitch.disable(FeatureSwitch.forName(fs.name)) - } - ) - Future(Accepted(currentFeatureSwitchesAsJson)) - } - } - } - - private def currentFeatureSwitchesAsJson = Json.toJson(for (fs <- UserInfoFeatureSwitches.allSwitches) yield FeatureSwitch(fs.name, fs.isEnabled)) -} - -case class FeatureSwitchRequest(featureSwitches: Seq[FeatureSwitch]) {} diff --git a/app/data/UserInfoGenerator.scala b/app/data/UserInfoGenerator.scala index 744624a..7f7bda8 100644 --- a/app/data/UserInfoGenerator.scala +++ b/app/data/UserInfoGenerator.scala @@ -19,7 +19,6 @@ package data import javax.inject.Singleton import java.time.LocalDate import uk.gov.hmrc.auth.core.{Enrolment, EnrolmentIdentifier} -import config.UserInfoFeatureSwitches import domain.{Address, GovernmentGatewayDetails, Mdtp, UserInfo} import scala.util.Random.{nextInt => randomNextInt} @@ -75,27 +74,21 @@ class UserInfoGenerator { ) ) - def addressWithToggleableFeatures(isAddressLine5: Boolean = false, isCountryCode: Boolean = false): Option[Address] = { - val addressLine5 = if (isAddressLine5) "\n|Line5" else "" - val code = if (isCountryCode) Some("GB") else None - + def address: Option[Address] = Some( Address( s"""221B Baker Street - |Town centre - |London - |England$addressLine5 - |NW1 9NT - |Great Britain""".stripMargin, + |Town centre + |London + |England + |Line5 + |NW1 9NT + |Great Britain""".stripMargin, Some("NW1 9NT"), Some("Great Britain"), - code + Some("GB") ) ) - } - - def address: Option[Address] = - addressWithToggleableFeatures(UserInfoFeatureSwitches.addressLine5.isEnabled, UserInfoFeatureSwitches.countryCode.isEnabled) val enrolments: Set[Enrolment] = Set(Enrolment("IR-SA", List(EnrolmentIdentifier("UTR", "174371121")), "Activated")) private val government_gateway_v1_0: GovernmentGatewayDetails = GovernmentGatewayDetails( diff --git a/app/services/UserInfoTransformer.scala b/app/services/UserInfoTransformer.scala index 0f7d691..36bea80 100644 --- a/app/services/UserInfoTransformer.scala +++ b/app/services/UserInfoTransformer.scala @@ -20,7 +20,6 @@ import javax.inject.Singleton import java.time.LocalDate import uk.gov.hmrc.auth.core.Enrolments import uk.gov.hmrc.auth.core.retrieve.ItmpAddress -import config.UserInfoFeatureSwitches import domain._ @Singleton @@ -39,10 +38,8 @@ class UserInfoTransformer { def address = if (scopes.contains("address")) { val countryName = desUserInfo flatMap { c => c.address.countryName } - val countryCode = if (UserInfoFeatureSwitches.countryCode.isEnabled) { desUserInfo flatMap { u => u.address.countryCode } } - else None + val countryCode = desUserInfo flatMap { u => u.address.countryCode } desUserInfo map (u => Address(formattedAddress(u.address), u.address.postCode, countryName, countryCode)) - } else None val identifier = if (scopes.contains("openid:gov-uk-identifiers")) authority flatMap (_.nino) else None @@ -74,7 +71,7 @@ class UserInfoTransformer { private def formattedAddress(desAddress: ItmpAddress) = { val countryName = desAddress.countryName - val addressLine5 = if (UserInfoFeatureSwitches.addressLine5.isEnabled) desAddress.line5 else None + val addressLine5 = desAddress.line5 Seq(desAddress.line1, desAddress.line2, desAddress.line3, desAddress.line4, addressLine5, desAddress.postCode, countryName).flatten.mkString("\n") } diff --git a/conf/application.conf b/conf/application.conf index ad50ff7..2989f3a 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -54,9 +54,6 @@ play.i18n.langs = ["en"] # !!!WARNING!!! DO NOT CHANGE THIS ROUTER play.http.router = prod.Routes -feature.addressLine5 = false -feature.countryCode = false - # Controller # ~~~~~ # By default all controllers will have authorisation, logging and @@ -84,12 +81,6 @@ controllers { needsLogging = true needsAuditing = false } - - controllers.testOnly.FeatureSwitchController = { - needsAuth = false - needsLogging = true - needsAuditing = false - } } # Root logger: diff --git a/conf/testOnlyDoNotUseInAppConf.routes b/conf/testOnlyDoNotUseInAppConf.routes index bd6ee64..ac633df 100644 --- a/conf/testOnlyDoNotUseInAppConf.routes +++ b/conf/testOnlyDoNotUseInAppConf.routes @@ -1,4 +1 @@ -GET /test-only/feature-switches @controllers.testOnly.FeatureSwitchController.getFlags -POST /test-only/feature-switches @controllers.testOnly.FeatureSwitchController.setFlags - -> / prod.Routes \ No newline at end of file diff --git a/it/test/FeatureSwitchControllerISpec.scala b/it/test/FeatureSwitchControllerISpec.scala deleted file mode 100644 index 4d68650..0000000 --- a/it/test/FeatureSwitchControllerISpec.scala +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2024 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. - */ - -import play.api.libs.json.Json -import scalaj.http.Http - -class FeatureSwitchControllerISpec extends BaseFeatureISpec { - - val serviceUrl = "/test-only/feature-switches" - - Feature("getting feature switches") { - Scenario("calling GET /test-only/feature-switches") { - Given("we call GET /test-only/feature-switches") - - val result = Http(resource(s"$serviceUrl")).asString - - Then("the feature switches are returned as json with 200 OK") - - result.code shouldBe 200 - Json.parse(result.body) shouldBe Json.arr(Json.obj("name" -> "countryCode", "isEnabled" -> false), - Json.obj("name" -> "addressLine5", "isEnabled" -> false) - ) - } - } - - Feature("updating feature switches") { - Scenario("calling POST /test-only/feature-switches") { - Given("we call GET /test-only/feature-switches") - - val result = Http(resource(s"$serviceUrl")).asString - - Then("the feature switches are returned as json with 200 OK") - - result.code shouldBe 200 - Json.parse(result.body) shouldBe Json.arr(Json.obj("name" -> "countryCode", "isEnabled" -> false), - Json.obj("name" -> "addressLine5", "isEnabled" -> false) - ) - - When("we update the flags we should get 406 Accepted") - - val payload = Json.obj( - "featureSwitches" -> Json.arr(Json.obj("name" -> "countryCode", "isEnabled" -> true), Json.obj("name" -> "addressLine5", "isEnabled" -> true)) - ) - - val updateResult = - Http(resource(s"$serviceUrl")).method("POST").header("Content-Type", "application/json").postData(payload.toString()).asString - - updateResult.code shouldBe 202 - - When("we retrieve the flags back we see they should be negated") - - val updatedResult = Http(resource(s"$serviceUrl")).asString - - updatedResult.code shouldBe 200 - Json.parse(updatedResult.body) shouldBe Json.arr(Json.obj("name" -> "countryCode", "isEnabled" -> true), - Json.obj("name" -> "addressLine5", "isEnabled" -> true) - ) - - } - } -} diff --git a/it/test/UserInfoServiceISpec.scala b/it/test/UserInfoServiceISpec.scala index a60bee3..d2aba4a 100644 --- a/it/test/UserInfoServiceISpec.scala +++ b/it/test/UserInfoServiceISpec.scala @@ -18,7 +18,6 @@ import java.nio.file.Paths import com.fasterxml.jackson.databind.ObjectMapper import com.github.fge.jsonschema.core.report.LogLevel import com.github.fge.jsonschema.main.JsonSchemaFactory -import config.{FeatureSwitch, UserInfoFeatureSwitches} import domain._ import java.time.LocalDate @@ -29,21 +28,7 @@ import uk.gov.hmrc.auth.core._ import uk.gov.hmrc.auth.core.retrieve._ import uk.gov.hmrc.domain.Nino -import scala.jdk.CollectionConverters.CollectionHasAsScala - class UserInfoServiceISpec extends BaseFeatureISpec with AuthStub with ThirdPartyDelegatedAuthorityStub { - - override def beforeAll(): Unit = { - super.beforeAll() - FeatureSwitch.enable(UserInfoFeatureSwitches.countryCode) - FeatureSwitch.enable(UserInfoFeatureSwitches.addressLine5) - } - override def afterAll(): Unit = { - super.afterAll() - FeatureSwitch.disable(UserInfoFeatureSwitches.countryCode) - FeatureSwitch.disable(UserInfoFeatureSwitches.addressLine5) - } - val serviceUrl: String = resource("") val authorizationTokens = "AUTHORIZATION_TOKENS" diff --git a/project/AppDependencies.scala b/project/AppDependencies.scala index 55dfdff..c8b33e7 100644 --- a/project/AppDependencies.scala +++ b/project/AppDependencies.scala @@ -4,7 +4,7 @@ import sbt.* object AppDependencies { - private val bootstrapPlayVersion = "8.5.0" + private val bootstrapPlayVersion = "8.6.0" private val compile: Seq[ModuleID] = Seq( ws, @@ -14,19 +14,9 @@ object AppDependencies { ) private val test: Seq[ModuleID] = Seq( - "org.scalatest" %% "scalatest" % "3.2.17" % Test, - "org.scalatestplus" %% "scalacheck-1-17" % "3.2.17.0" % Test, - "org.scalatestplus.play" %% "scalatestplus-play" % "5.1.0" % Test, - "org.playframework" %% "play-test" % PlayVersion.current % Test, - "com.github.tomakehurst" % "wiremock" % "2.27.2" % Test, - "uk.gov.hmrc" %% "bootstrap-test-play-30" % bootstrapPlayVersion % Test, - "org.pegdown" % "pegdown" % "1.6.0" % Test, - "org.jsoup" % "jsoup" % "1.17.2" % Test, - "org.scalaj" %% "scalaj-http" % "2.4.2" % Test, - "org.mockito" %% "mockito-scala-scalatest" % "1.17.30" % Test, - "org.scalacheck" %% "scalacheck" % "1.17.0" % Test, - "com.github.java-json-tools" % "json-schema-validator" % "2.2.14" % Test, - "com.vladsch.flexmark" % "flexmark-all" % "0.64.0" % Test + "org.scalaj" %% "scalaj-http" % "2.4.2" % Test, + "com.github.java-json-tools" % "json-schema-validator" % "2.2.14" % Test, + "uk.gov.hmrc" %% "bootstrap-test-play-30" % "8.6.0" % Test ) def apply(): Seq[ModuleID] = compile ++ test diff --git a/project/build.properties b/project/build.properties index d415199..4d5f78c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.7 \ No newline at end of file +sbt.version=1.9.9 \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index 2942aa1..a66ff56 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,9 +2,9 @@ resolvers += "HMRC-open-artefacts-maven" at "https://open.artefacts.tax.service. resolvers += Resolver.url("HMRC-open-artefacts-ivy", url("https://open.artefacts.tax.service.gov.uk/ivy2"))(Resolver.ivyStylePatterns) -addSbtPlugin("uk.gov.hmrc" % "sbt-auto-build" % "3.20.0") +addSbtPlugin("uk.gov.hmrc" % "sbt-auto-build" % "3.22.0") addSbtPlugin("uk.gov.hmrc" % "sbt-distributables" % "2.5.0") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11") -addSbtPlugin("org.playframework" % "sbt-plugin" % "3.0.2") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12") +addSbtPlugin("org.playframework" % "sbt-plugin" % "3.0.3") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0" exclude("org.scala-lang.modules", "scala-xml_2.12")) addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") \ No newline at end of file diff --git a/public/api/conf/1.0/application.yaml b/public/api/conf/1.0/application.yaml index 5151476..22fc7c9 100644 --- a/public/api/conf/1.0/application.yaml +++ b/public/api/conf/1.0/application.yaml @@ -2,15 +2,20 @@ openapi: 3.0.3 info: title: User Information description: >- - Access to user information is controlled through scopes. Each access token (OAuth 2.0 Bearer Token) is associated with a set of scopes at login. When a request is made for user information, only information belonging to the provided scopes is returned. The information is returned in the form of claims, which sometimes are simple fields and sometimes objects that contain further fields. - Here is a list of supported scopes and the claims they contain. The details of each claim, including any contained fields, is documented further down. - * 'profile': given_name, middle_name, family_name, birthdate - * 'address': address - * 'email': email - * 'openid:hmrc-enrolments': hmrc_enrolments - * 'openid:government-gateway': government_gateway - * 'openid:mdtp': mdtp - * 'openid:gov-uk-identifiers': uk_gov_nino + Only the GET method is supported.

+ + Access to user information is controlled through scopes. Each access token (OAuth 2.0 Bearer Token) is associated with a set of scopes at login.

+ + When a request is made for user information, only information belonging to the provided scopes is returned. The information is returned in the form of claims, which sometimes are simple fields and sometimes objects that contain further fields.

+ + Here is the supported scope list and the claims they contain. The details of each claim, including any contained fields, is documented further down.
+ * 'profile': given_name, middle_name, family_name, birthdate
+ * 'address': address
+ * 'email': email
+ * 'openid:hmrc-enrolments': hmrc_enrolments
+ * 'openid:government-gateway': government_gateway
+ * 'openid:mdtp': mdtp
+ * 'openid:gov-uk-identifiers': uk_gov_nino
contact: {} version: '1.0' servers: @@ -60,7 +65,7 @@ paths: value: '174371121' state: activated government_gateway: - user_id: '019283' + user_id: '019283739713' roles: - User affinity_group: Individual @@ -98,7 +103,7 @@ paths: value: '174371121' state: activated government_gateway: - user_id: '019283' + user_id: '019283739713' roles: - User affinity_group: Individual @@ -161,7 +166,7 @@ components: properties: formatted: type: string - description: End-user's mailing address, formatted for display or use on a mailing label. + description: End-User's mailing address, formatted for display or use on a mailing label. example: >- 221B Baker Street @@ -172,15 +177,15 @@ components: Great Britain postal_code: type: string - description: End-user's Zip code or postal code. + description: End-User's Zip code or postal code. example: NW1 9NT country: type: string - description: End-user's country name. + description: End-User's country name. example: Great Britain country_code: type: string - description: ISO 3166 Alpha-2-code of a given country + description: Take the first 2 chars to obtain the ISO 3166 Alpha-2-code of a given country example: GB GovernmentGateway: title: GovernmentGateway @@ -241,14 +246,14 @@ components: type: array items: $ref: '#/components/schemas/Identifier' - description: End-user's identifiers associated to this HMRC service. + description: End-User's identifiers associated to this HMRC service. example: - key: UTR value: '174371121' state: allOf: - $ref: '#/components/schemas/State' - - description: End-user's HMRC enrolment status. + - description: End-User's HMRC enrolment status. Identifier: title: Identifier required: @@ -274,7 +279,7 @@ components: type: string description: Session id example: '2012345' - description: Mdtp information based on government gateway input + description: MDTP platform information on the End-User’s session for auditing & log correlation purposes State: title: State enum: @@ -285,14 +290,14 @@ components: - pending - givenToAgent type: string - description: End-user's HMRC enrolment status. + description: End-User's HMRC enrolment status. Userinforesponse: title: Userinforesponse type: object properties: given_name: type: string - description: End-user's first name. + description: End-User's first name. example: Thomas middle_name: type: string @@ -300,7 +305,7 @@ components: example: A. family_name: type: string - description: End-user's last name. + description: End-User's last name. example: Delgado email: type: string @@ -320,7 +325,7 @@ components: type: array items: $ref: '#/components/schemas/HmrcEnrolment' - description: End-user's HMRC enrolments. + description: End-User's HMRC enrolments. mdtp: allOf: - $ref: '#/components/schemas/Mdtp' diff --git a/test/connectors/ThirdPartyDelegatedAuthorityConnectorSpec.scala b/test/connectors/ThirdPartyDelegatedAuthorityConnectorSpec.scala index d00af4b..3384265 100644 --- a/test/connectors/ThirdPartyDelegatedAuthorityConnectorSpec.scala +++ b/test/connectors/ThirdPartyDelegatedAuthorityConnectorSpec.scala @@ -23,7 +23,8 @@ import com.github.tomakehurst.wiremock.core.WireMockConfiguration._ import org.scalatest.BeforeAndAfterEach import org.scalatestplus.play.guice.GuiceOneAppPerSuite import config.AppContext -import org.mockito.scalatest.MockitoSugar +import org.mockito.Mockito.when +import org.scalatestplus.mockito.MockitoSugar import uk.gov.hmrc.http.{HeaderCarrier, HttpClient} import scala.concurrent.ExecutionContext.Implicits.global diff --git a/test/controllers/UserInfoControllerSpec.scala b/test/controllers/UserInfoControllerSpec.scala index 8fa24d7..5966e6a 100644 --- a/test/controllers/UserInfoControllerSpec.scala +++ b/test/controllers/UserInfoControllerSpec.scala @@ -19,11 +19,12 @@ package controllers import config.AppContext import domain.{Address, GovernmentGatewayDetails, UserInfo} import org.apache.pekko.actor.ActorSystem +import org.mockito.ArgumentMatchers.{any, eq => eqTo} import java.time.LocalDate import org.mockito.BDDMockito.given import org.scalatest.concurrent.ScalaFutures -import org.mockito.scalatest.MockitoSugar +import org.scalatestplus.mockito.MockitoSugar import play.api.libs.json.Json import play.api.mvc.ControllerComponents import play.api.test.FakeRequest diff --git a/test/controllers/testOnly/FeatureSwitchControllerSpec.scala b/test/controllers/testOnly/FeatureSwitchControllerSpec.scala deleted file mode 100644 index c2190e0..0000000 --- a/test/controllers/testOnly/FeatureSwitchControllerSpec.scala +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2024 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 controllers.testOnly - -import org.scalatest.concurrent.ScalaFutures -import play.api.libs.json._ -import play.api.mvc.{ControllerComponents, Request, Result} -import play.api.test.FakeRequest -import config.{FeatureSwitch, UserInfoFeatureSwitches} -import org.apache.pekko.actor.ActorSystem -import testSupport.UnitSpec - -import scala.concurrent.ExecutionContext - -class FeatureSwitchControllerSpec(implicit val cc: ControllerComponents, val ex: ExecutionContext) extends UnitSpec with ScalaFutures { - - implicit val actorSystem: ActorSystem = ActorSystem("test") - - trait Setup { - def featuresList(countryCodeEnabled: Boolean = false, addressLine5Enabled: Boolean = false): JsValue = Json.arr( - Json.obj("name" -> "countryCode", "isEnabled" -> countryCodeEnabled), - Json.obj("name" -> "addressLine5", "isEnabled" -> addressLine5Enabled) - ) - - val controller = new FeatureSwitchController - UserInfoFeatureSwitches.allSwitches.map(FeatureSwitch.disable(_)) - } - - "FeatureSwitchController" should { - "return a list of switches" in new Setup { - val expectedBody = featuresList() - - val result = await(controller.getFlags()(FakeRequest())) - status(result) shouldBe 200 - jsonBodyOf(result) shouldBe expectedBody - } - - "enable the country code" in new Setup { - val expectedBody = featuresList(countryCodeEnabled = true) - - val changeRequest = Json.obj("featureSwitches" -> Json.arr(Json.obj("name" -> "countryCode", "isEnabled" -> true))) - val updateRequest: Request[JsValue] = FakeRequest().withBody(changeRequest) - - val result: Result = await(controller.setFlags()(updateRequest)) - - status(result) shouldBe 202 - jsonBodyOf(result) shouldBe expectedBody - } - - "disable the address line 5" in new Setup { - val expectedBody = featuresList() - - val changeRequest = Json.obj("featureSwitches" -> Json.arr(Json.obj("name" -> "addressLine5", "isEnabled" -> false))) - val updateRequest: Request[JsValue] = FakeRequest().withBody(changeRequest) - - val result: Result = await(controller.setFlags()(updateRequest)) - - status(result) shouldBe 202 - jsonBodyOf(result) shouldBe expectedBody - } - } -} diff --git a/test/data/UserInfoGeneratorSpec.scala b/test/data/UserInfoGeneratorSpec.scala index 26e95bb..e645fec 100644 --- a/test/data/UserInfoGeneratorSpec.scala +++ b/test/data/UserInfoGeneratorSpec.scala @@ -18,7 +18,6 @@ package data import java.time.LocalDate import org.scalatest.BeforeAndAfterEach -import config.{FeatureSwitch, UserInfoFeatureSwitches} import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec @@ -27,16 +26,6 @@ class UserInfoGeneratorSpec extends AnyWordSpec with Matchers with BeforeAndAfte private val from = LocalDate.of(1939, 12, 27) private val until = LocalDate.of(1998, 12, 29) - override protected def beforeEach(): Unit = { - FeatureSwitch.enable(UserInfoFeatureSwitches.countryCode) - FeatureSwitch.enable(UserInfoFeatureSwitches.addressLine5) - } - - override protected def afterEach(): Unit = { - FeatureSwitch.disable(UserInfoFeatureSwitches.countryCode) - FeatureSwitch.disable(UserInfoFeatureSwitches.addressLine5) - } - "userInfo" should { "generate an OpenID Connect compliant UserInfo response v1.0" in { val userInfo = TestUserInfoGenerator.userInfoV1_0() @@ -49,18 +38,6 @@ class UserInfoGeneratorSpec extends AnyWordSpec with Matchers with BeforeAndAfte userInfo.government_gateway.get.profile_uri should not be defined userInfo.government_gateway.get.group_profile_uri should not be defined } - - "generate an OpenID Connect compliant UserInfo response without country code when feature flag is disabled" in { - FeatureSwitch.disable(UserInfoFeatureSwitches.countryCode) - val userInfo = TestUserInfoGenerator.userInfoV1_0() - userInfo.address shouldBe TestUserInfoGenerator.addressWithToggleableFeatures(isAddressLine5 = true, isCountryCode = false) - } - - "generate an OpenID Connect UserInfo response without addressLine5 when feature flag is disabled" in { - FeatureSwitch.disable(UserInfoFeatureSwitches.addressLine5) - val userInfo = TestUserInfoGenerator.userInfoV1_0() - userInfo.address shouldBe TestUserInfoGenerator.addressWithToggleableFeatures(isAddressLine5 = false, isCountryCode = true) - } } private def assertValidDob(dob: LocalDate): Unit = diff --git a/test/services/UserInfoServiceSpec.scala b/test/services/UserInfoServiceSpec.scala index b52abd4..13f90a0 100644 --- a/test/services/UserInfoServiceSpec.scala +++ b/test/services/UserInfoServiceSpec.scala @@ -20,9 +20,11 @@ import connectors.{AuthConnector, AuthConnectorV1, ThirdPartyDelegatedAuthorityC import controllers.Version_1_0 import data.UserInfoGenerator import domain._ +import org.mockito.ArgumentMatchers.{any, eq => eqTo} import org.mockito.BDDMockito.given +import org.mockito.Mockito.{never, verify, when} import org.scalatest.concurrent.ScalaFutures -import org.mockito.scalatest.MockitoSugar +import org.scalatestplus.mockito.MockitoSugar import testSupport.UnitSpec import uk.gov.hmrc.auth.core.retrieve.{ItmpAddress, ItmpName} import uk.gov.hmrc.auth.core.{Enrolment, EnrolmentIdentifier, Enrolments} diff --git a/test/services/UserInfoTransformerSpec.scala b/test/services/UserInfoTransformerSpec.scala index 534e7e1..453ebcd 100644 --- a/test/services/UserInfoTransformerSpec.scala +++ b/test/services/UserInfoTransformerSpec.scala @@ -16,11 +16,11 @@ package services -import config.{FeatureSwitch, UserInfoFeatureSwitches} import domain._ + import java.time.LocalDate import org.scalatest.BeforeAndAfterEach -import org.mockito.scalatest.MockitoSugar +import org.scalatestplus.mockito.MockitoSugar import testSupport.UnitSpec import uk.gov.hmrc.auth.core.retrieve.{GatewayInformation, ItmpAddress, ItmpName, MdtpInformation} import uk.gov.hmrc.auth.core.{Enrolment, EnrolmentIdentifier, Enrolments} @@ -94,16 +94,6 @@ class UserInfoTransformerSpec extends UnitSpec with MockitoSugar with BeforeAndA Some(mdtp) ) - override protected def beforeEach() = { - FeatureSwitch.enable(UserInfoFeatureSwitches.countryCode) - FeatureSwitch.enable(UserInfoFeatureSwitches.addressLine5) - } - - override protected def afterEach() = { - FeatureSwitch.disable(UserInfoFeatureSwitches.countryCode) - FeatureSwitch.disable(UserInfoFeatureSwitches.addressLine5) - } - trait Setup { implicit val hc: HeaderCarrier = HeaderCarrier() @@ -252,17 +242,60 @@ class UserInfoTransformerSpec extends UnitSpec with MockitoSugar with BeforeAndA result shouldBe userInfoMissingPostCode } - "not return country code when feature flag is off" in new Setup { + "return object containing country code if country code is defined in DES response" in new Setup { - FeatureSwitch.disable(UserInfoFeatureSwitches.countryCode) val scopes = Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc-enrolments", "openid:government-gateway", "email", "openid:mdtp") val result = await(transformer.transform(scopes, Some(authority), Some(desUserInfo), Some(enrolments), Some(userDetails))) - val userInfoMissingCountryCode = userInfo.copy(address = - Some(userAddress.copy(formatted = "1 Station Road\nTown Centre\nLondon\nEngland\nUK\nNW1 6XE\nUnited Kingdom", country_code = None)) + result.address should be(defined) + result.address.get.country_code shouldBe desUserInfo.address.countryCode + } + + "return object not containing country code if country code isn't defined in DES response" in new Setup { + + val scopes = + Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc-enrolments", "openid:government-gateway", "email", "openid:mdtp") + val result = await( + transformer.transform(scopes, + Some(authority), + Some(desUserInfo.copy(address = desAddress.copy(countryCode = None))), + Some(enrolments), + Some(userDetails) + ) ) - result shouldBe userInfoMissingCountryCode + + result.address should be(defined) + result.address.get.country_code shouldBe None + } + + "return object containing line 5 if line 5 is defined in DES response" in new Setup { + + val scopes = + Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc-enrolments", "openid:government-gateway", "email", "openid:mdtp") + val result = await(transformer.transform(scopes, Some(authority), Some(desUserInfo), Some(enrolments), Some(userDetails))) + + val userInfoWithFormattedAddress = + userInfo.copy(address = Some(userAddress.copy(formatted = "1 Station Road\nTown Centre\nLondon\nEngland\nUK\nNW1 6XE\nUnited Kingdom"))) + result shouldBe userInfoWithFormattedAddress + } + + "return object not containing line 5 if line 5 isn't defined in DES response" in new Setup { + + val scopes = + Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc-enrolments", "openid:government-gateway", "email", "openid:mdtp") + val result = await( + transformer.transform(scopes, + Some(authority), + Some(desUserInfo.copy(address = desAddress.copy(line5 = None))), + Some(enrolments), + Some(userDetails) + ) + ) + + val userInfoWithFormattedAddress = + userInfo.copy(address = Some(userAddress.copy(formatted = "1 Station Road\nTown Centre\nLondon\nEngland\nNW1 6XE\nUnited Kingdom"))) + result shouldBe userInfoWithFormattedAddress } } }