diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/config/AppConfig.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/config/AppConfig.scala index 725ebc5..68aff6a 100755 --- a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/config/AppConfig.scala +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/config/AppConfig.scala @@ -26,4 +26,7 @@ class AppConfig @Inject()(config: Configuration, servicesConfig: ServicesConfig) lazy val serviceSignOut:String = servicesConfig.getString("service-signout.url") lazy val ITSAPenaltiesAppealsHomeUrl = "/penalties-appeals/income-tax" val alphaBannerUrl: String = servicesConfig.getString("alpha-banner-url") + def getFeatureSwitchValue(feature: String): Boolean = config.get[Boolean](feature) + def selfUrl: String = servicesConfig.baseUrl("income-tax-penalties-appeals-frontend") + } diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/LanguageSwitchController.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/LanguageSwitchController.scala index 54bc27b..934e18c 100644 --- a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/LanguageSwitchController.scala +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/LanguageSwitchController.scala @@ -31,5 +31,5 @@ class LanguageSwitchController @Inject()( "cymraeg" -> Lang("cy") ) - override def fallbackURL: String = routes.HelloWorldController.helloWorld.url + override def fallbackURL: String = routes.ServiceController.helloWorld.url } diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/HelloWorldController.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceController.scala similarity index 97% rename from app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/HelloWorldController.scala rename to app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceController.scala index 1d2f506..f089065 100755 --- a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/HelloWorldController.scala +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceController.scala @@ -25,7 +25,7 @@ import javax.inject.{Inject, Singleton} import scala.concurrent.Future @Singleton -class HelloWorldController @Inject()( +class ServiceController @Inject()( mcc: MessagesControllerComponents, helloWorldPage: HelloWorldPage)(appConfig: AppConfig) extends FrontendController(mcc) { diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/api/controllers/FeatureSwitchApiController.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/api/controllers/FeatureSwitchApiController.scala new file mode 100644 index 0000000..496bc98 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/api/controllers/FeatureSwitchApiController.scala @@ -0,0 +1,42 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.api.controllers + +import play.api.libs.json.Json +import play.api.mvc.{Action, AnyContent, InjectedController} +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.api.services.FeatureSwitchService +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.config.AppConfig +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.config.FeatureSwitching +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models.FeatureSwitchSetting + +import javax.inject.{Inject, Singleton} + +@Singleton +class FeatureSwitchApiController @Inject()(featureSwitchService: FeatureSwitchService, + val config: AppConfig) extends InjectedController with FeatureSwitching { + + def getFeatureSwitches: Action[AnyContent] = Action { + Ok(Json.toJson(featureSwitchService.getFeatureSwitches())) + } + + def updateFeatureSwitches(): Action[Seq[FeatureSwitchSetting]] = Action(parse.json[Seq[FeatureSwitchSetting]]) { + req => + val updatedFeatureSwitches = featureSwitchService.updateFeatureSwitches(req.body) + Ok(Json.toJson(updatedFeatureSwitches)) + } + +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/api/services/FeatureSwitchService.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/api/services/FeatureSwitchService.scala new file mode 100644 index 0000000..d1714c6 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/api/services/FeatureSwitchService.scala @@ -0,0 +1,50 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.api.services + +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.config.AppConfig +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.config.{FeatureSwitchRegistry, FeatureSwitching} +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models.FeatureSwitchSetting + +import javax.inject.{Inject, Singleton} + +@Singleton +class FeatureSwitchService @Inject()(featureSwitchRegistry: FeatureSwitchRegistry, + val config: AppConfig) extends FeatureSwitching { + + def getFeatureSwitches(): Seq[FeatureSwitchSetting] = + featureSwitchRegistry.switches.map( + switch => + FeatureSwitchSetting( + switch.configName, + switch.displayName, + isEnabled(switch) + ) + ) + + def updateFeatureSwitches(updatedFeatureSwitches: Seq[FeatureSwitchSetting]): Seq[FeatureSwitchSetting] = { + updatedFeatureSwitches.foreach( + featureSwitchSetting => + featureSwitchRegistry.get(featureSwitchSetting.configName).foreach { + featureSwitch => + if (featureSwitchSetting.isEnabled) enable(featureSwitch) else disable(featureSwitch) + } + ) + + getFeatureSwitches() + } +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitchRegistry.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitchRegistry.scala new file mode 100644 index 0000000..cc86cb9 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitchRegistry.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.config + +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models.FeatureSwitch + +trait FeatureSwitchRegistry { + + def switches: Seq[FeatureSwitch] + + def apply(name: String): FeatureSwitch = + get(name) match { + case Some(switch) => switch + case None => throw new IllegalArgumentException("Invalid feature switch: " + name) + } + + def get(name: String): Option[FeatureSwitch] = switches find (_.configName == name) + +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitching.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitching.scala new file mode 100644 index 0000000..a679389 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitching.scala @@ -0,0 +1,46 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.config + +import play.api.Logging +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.config.AppConfig +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models.FeatureSwitch + +trait FeatureSwitching extends Logging { + + val config: AppConfig + + val FEATURE_SWITCH_ON = "true" + val FEATURE_SWITCH_OFF = "false" + + def isEnabled(featureSwitch: FeatureSwitch): Boolean = + sys.props get featureSwitch.configName match { + case Some(value) => value == FEATURE_SWITCH_ON + case None => config.getFeatureSwitchValue(featureSwitch.configName) + } + + def enable(featureSwitch: FeatureSwitch): Unit = { + logger.warn(s"[enable] $featureSwitch") + sys.props += featureSwitch.configName -> FEATURE_SWITCH_ON + } + + def disable(featureSwitch: FeatureSwitch): Unit = { + logger.warn(s"[disable] $featureSwitch") + sys.props += featureSwitch.configName -> FEATURE_SWITCH_OFF + } + +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitchingModule.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitchingModule.scala new file mode 100644 index 0000000..3fbef88 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/config/FeatureSwitchingModule.scala @@ -0,0 +1,42 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.config + +import play.api.inject.{Binding, Module} +import play.api.{Configuration, Environment} +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models.FeatureSwitch + +import javax.inject.Singleton + +@Singleton +class FeatureSwitchingModule extends Module with FeatureSwitchRegistry { + + val switches: Seq[FeatureSwitch] = Seq( + UseStubForBackend + ) + + override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = { + Seq( + bind[FeatureSwitchRegistry].to(this).eagerly() + ) + } +} + +case object UseStubForBackend extends FeatureSwitch { + override val configName: String = "features.useStubForBackend" + override val displayName: String = "Use stub instead of Penalties backend service" +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/models/FeatureSwitch.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/models/FeatureSwitch.scala new file mode 100644 index 0000000..0b2526a --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/models/FeatureSwitch.scala @@ -0,0 +1,22 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models + +trait FeatureSwitch { + val configName: String + val displayName: String +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/models/FeatureSwitchSetting.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/models/FeatureSwitchSetting.scala new file mode 100644 index 0000000..bda0b3f --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/core/models/FeatureSwitchSetting.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models + +import play.api.libs.json.{Json, OFormat} + + +case class FeatureSwitchSetting(configName: String, + displayName: String, + isEnabled: Boolean) + +object FeatureSwitchSetting { + + implicit val format: OFormat[FeatureSwitchSetting] = Json.format[FeatureSwitchSetting] + +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/config/FeatureSwitchProviderConfig.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/config/FeatureSwitchProviderConfig.scala new file mode 100644 index 0000000..1a4691d --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/config/FeatureSwitchProviderConfig.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.config + +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.config.AppConfig +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.models.FeatureSwitchProvider + +import javax.inject.{Inject, Singleton} + +@Singleton +class FeatureSwitchProviderConfig @Inject()(appConfig: AppConfig) { + + lazy val selfBaseUrl: String = appConfig.selfUrl + + lazy val selfFeatureSwitchUrl = s"$selfBaseUrl/penalties-appeals/income-tax/test-only/api/feature-switches" + + lazy val selfFeatureSwitchProvider: FeatureSwitchProvider = FeatureSwitchProvider( + id = "income-tax-penalties-appeals-frontend", + appName = "Income Tax Penalties Appeals Frontend", + url = selfFeatureSwitchUrl + ) + + lazy val featureSwitchProviders: Seq[FeatureSwitchProvider] = + Seq(selfFeatureSwitchProvider) + +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/connectors/FeatureSwitchApiConnector.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/connectors/FeatureSwitchApiConnector.scala new file mode 100644 index 0000000..0be1eb7 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/connectors/FeatureSwitchApiConnector.scala @@ -0,0 +1,67 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.connectors + +import play.api.http.Status._ +import play.api.libs.json.{JsError, JsSuccess, Json, Reads} +import uk.gov.hmrc.http.HeaderCarrier +import uk.gov.hmrc.http.HttpReads.Implicits.readRaw +import uk.gov.hmrc.http.client.HttpClientV2 +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models.FeatureSwitchSetting + +import java.net.URL +import javax.inject.{Inject, Singleton} +import scala.concurrent.{ExecutionContext, Future} + +@Singleton +class FeatureSwitchApiConnector @Inject()(httpClient: HttpClientV2)(implicit ec: ExecutionContext) { + + def retrieveFeatureSwitches(featureSwitchProviderUrl: String + )(implicit reads: Reads[Seq[FeatureSwitchSetting]], + hc: HeaderCarrier): Future[Seq[FeatureSwitchSetting]] = { + httpClient.get(new URL(featureSwitchProviderUrl)).execute.map( + response => + response.status match { + case OK => + response.json.validate[Seq[FeatureSwitchSetting]] match { + case JsSuccess(settings, _) => settings + case JsError(errors) => throw new Exception(errors.head.toString) + } + case _ => throw new Exception(s"Could not retrieve feature switches from $featureSwitchProviderUrl") + } + ) + } + + def updateFeatureSwitches(featureSwitchProviderUrl: String, + featureSwitchSettings: Seq[FeatureSwitchSetting] + )(implicit hc: HeaderCarrier): Future[Seq[FeatureSwitchSetting]] = { + httpClient + .post(new URL(featureSwitchProviderUrl)) + .withBody(Json.toJson(featureSwitchSettings)).execute.map { + response => + response.status match { + case OK => + response.json.validate[Seq[FeatureSwitchSetting]] match { + case JsSuccess(settings, _) => settings + case JsError(errors) => throw new Exception(errors.head.toString) + } + case _ => throw new Exception(s"Could not retrieve feature switches from $featureSwitchProviderUrl") + } + } + } + +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/controllers/FeatureSwitchFrontendController.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/controllers/FeatureSwitchFrontendController.scala new file mode 100644 index 0000000..66db613 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/controllers/FeatureSwitchFrontendController.scala @@ -0,0 +1,53 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.controllers + +import play.api.i18n.I18nSupport +import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.config.AppConfig +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.config.FeatureSwitching +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.services.FeatureSwitchRetrievalService +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.views.html.feature_switch +import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendController + +import javax.inject.{Inject, Singleton} +import scala.concurrent.ExecutionContext + +@Singleton +class FeatureSwitchFrontendController @Inject()(featureSwitchService: FeatureSwitchRetrievalService, + featureSwitchView: feature_switch, + mcc: MessagesControllerComponents + )(implicit ec: ExecutionContext, + val config: AppConfig) extends FrontendController(mcc) with FeatureSwitching with I18nSupport { + + + def show(): Action[AnyContent] = Action.async { + implicit req => + featureSwitchService.retrieveFeatureSwitches().map { + featureSwitches => + Ok(featureSwitchView(featureSwitches, routes.FeatureSwitchFrontendController.submit())) + } + } + + def submit(): Action[Map[String, Seq[String]]] = Action.async(parse.formUrlEncoded) { + implicit req => + featureSwitchService.updateFeatureSwitches(req.body.keys).map { + featureSwitches => + Ok(featureSwitchView(featureSwitches, routes.FeatureSwitchFrontendController.submit())) + } + } +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/models/FeatureSwitchProvider.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/models/FeatureSwitchProvider.scala new file mode 100644 index 0000000..94a9358 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/models/FeatureSwitchProvider.scala @@ -0,0 +1,21 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.models + +case class FeatureSwitchProvider(id: String, + appName: String, + url: String) diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/services/FeatureSwitchRetrievalService.scala b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/services/FeatureSwitchRetrievalService.scala new file mode 100644 index 0000000..1c4c0cc --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/services/FeatureSwitchRetrievalService.scala @@ -0,0 +1,81 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.services + +import uk.gov.hmrc.http.HeaderCarrier +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models.FeatureSwitchSetting +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.config.FeatureSwitchProviderConfig +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.connectors.FeatureSwitchApiConnector +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.models.FeatureSwitchProvider + +import javax.inject.Inject +import scala.concurrent.{ExecutionContext, Future} +import scala.util.matching.Regex + +class FeatureSwitchRetrievalService @Inject()(featureSwitchConfig: FeatureSwitchProviderConfig, + featureSwitchApiConnector: FeatureSwitchApiConnector) + (implicit ec: ExecutionContext) { + + def retrieveFeatureSwitches()(implicit hc: HeaderCarrier): Future[Seq[(FeatureSwitchProvider, Seq[FeatureSwitchSetting])]] = { + + val featureSwitchSeq: Seq[(FeatureSwitchProvider, Future[Seq[FeatureSwitchSetting]])] = + featureSwitchConfig.featureSwitchProviders.map { + featureSwitchProvider => + featureSwitchProvider -> featureSwitchApiConnector.retrieveFeatureSwitches(featureSwitchProvider.url) + } + + Future.traverse(featureSwitchSeq) { + case (featureSwitchProvider, futureSeqFeatureSwitchSetting) => + futureSeqFeatureSwitchSetting.map { + featureSwitchSettingSeq => featureSwitchProvider -> featureSwitchSettingSeq + } + } + } + + val featureSwitchKeyRegex: Regex = "(.+?)\\.(.+)".r + + def updateFeatureSwitches(updatedFeatureSwitchKeys: Iterable[String] + )(implicit hc: HeaderCarrier): Future[Seq[(FeatureSwitchProvider, Seq[FeatureSwitchSetting])]] = { + val updatedFeatureSwitches: Future[Seq[(FeatureSwitchProvider, Seq[FeatureSwitchSetting])]] = + retrieveFeatureSwitches().map { + currentFeatureSwitches => + currentFeatureSwitches.map { + case (featureSwitchProvider, providerFeatureSwitches) => + featureSwitchProvider -> providerFeatureSwitches.map { + currentFeatureSwitch => + val isEnabled = updatedFeatureSwitchKeys.exists { + case featureSwitchKeyRegex(microserviceKey, featureSwitchKey) => + microserviceKey == featureSwitchProvider.id && featureSwitchKey == currentFeatureSwitch.configName + case _ => + false + } + currentFeatureSwitch.copy(isEnabled = isEnabled) + } + } + } + + updatedFeatureSwitches.flatMap { + Future.traverse(_) { + case (featureSwitchProvider, featureSwitchSettings) => + featureSwitchApiConnector.updateFeatureSwitches(featureSwitchProvider.url, featureSwitchSettings).map { + updatedFeatureSwitches => featureSwitchProvider -> updatedFeatureSwitches + } + } + } + } + +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/views/feature_switch.scala.html b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/views/feature_switch.scala.html new file mode 100644 index 0000000..6debc67 --- /dev/null +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/featureswitch/frontend/views/feature_switch.scala.html @@ -0,0 +1,63 @@ +@* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *@ + +@import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.views.html.Layout +@import uk.gov.hmrc.govukfrontend.views.html.components._ +@import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.models.FeatureSwitchSetting +@import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.models.FeatureSwitchProvider + + +@this(layout: Layout, + govukCheckboxes: GovukCheckboxes, + govukButton: GovukButton, + formWithCSRF: FormWithCSRF +) + +@(featureSwitchList: Seq[(FeatureSwitchProvider, Seq[FeatureSwitchSetting])], formAction: Call)(implicit request: Request[_], messages: Messages) + +@layout(Some("Choose which features to enable.")) { +

Choose which features to enable.

+ + @formWithCSRF(action = formAction) { + @for(featureSwitches <- featureSwitchList) { + @govukCheckboxes(Checkboxes( + fieldset = Some(Fieldset( + legend = Some(Legend( + content = Text(featureSwitches._1.appName), + classes = "govuk-fieldset__legend--m", + isPageHeading = false + )) + )), + idPrefix = Some(featureSwitches._1.id), + name = "feature-switch", + items = featureSwitches._2.map { + featureSwitchSettings => + CheckboxItem( + id = Some(featureSwitchSettings.configName), + name = Some(s"${featureSwitches._1.id}.${featureSwitchSettings.configName}"), + content = Text(featureSwitchSettings.displayName), + checked = featureSwitchSettings.isEnabled + ) + } + )) + } + + @govukButton(Button( + classes = "govuk-!-margin-right-1", + content = Text("Submit") + )) + } +} diff --git a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/views/Layout.scala.html b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/views/Layout.scala.html index c218a00..58b7947 100755 --- a/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/views/Layout.scala.html +++ b/app/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/views/Layout.scala.html @@ -84,7 +84,7 @@ @hmrcReportTechnicalIssueHelper() } -@signOutUrl =@{uk.gov.hmrc.incometaxpenaltiesappealsfrontend.controllers.routes.HelloWorldController.logout.url} +@signOutUrl =@{uk.gov.hmrc.incometaxpenaltiesappealsfrontend.controllers.routes.ServiceController.logout.url} @hmrcStandardPage( HmrcStandardPageParams( diff --git a/conf/app.routes b/conf/app.routes index 7ea196d..c053a38 100755 --- a/conf/app.routes +++ b/conf/app.routes @@ -1,7 +1,7 @@ # microservice specific routes -> /hmrc-frontend hmrcfrontend.Routes -GET / uk.gov.hmrc.incometaxpenaltiesappealsfrontend.controllers.HelloWorldController.helloWorld -GET /logout uk.gov.hmrc.incometaxpenaltiesappealsfrontend.controllers.HelloWorldController.logout +GET / uk.gov.hmrc.incometaxpenaltiesappealsfrontend.controllers.ServiceController.helloWorld +GET /logout uk.gov.hmrc.incometaxpenaltiesappealsfrontend.controllers.ServiceController.logout GET /assets/*file controllers.Assets.versioned(path = "/public", file: Asset) GET /language/:lang uk.gov.hmrc.incometaxpenaltiesappealsfrontend.controllers.LanguageSwitchController.switchToLanguage(lang: String) diff --git a/conf/application.conf b/conf/application.conf index c1c4697..0767737 100755 --- a/conf/application.conf +++ b/conf/application.conf @@ -26,7 +26,7 @@ play.modules.enabled += "uk.gov.hmrc.play.bootstrap.HttpClientV2Module" play.http.errorHandler = "uk.gov.hmrc.incometaxpenaltiesappealsfrontend.config.ErrorHandler" # Play Modules -play.modules.enabled += "uk.gov.hmrc.incometaxpenaltiesappealsfrontend.config.Module" +play.modules.enabled += "uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.core.config.FeatureSwitchingModule" microservice { services { @@ -43,6 +43,10 @@ microservice { } } +features { + useStubForBackend = true +} + service-signout { url = "http://localhost:9514/feedback/income-tax-penalties-appeals-frontend" } diff --git a/conf/logback-test.xml b/conf/logback-test.xml new file mode 100644 index 0000000..340f0d5 --- /dev/null +++ b/conf/logback-test.xml @@ -0,0 +1,23 @@ + + + + + + ERROR + + + [%level] %message %replace(exception=[%xException]){'^exception=\[\]$',''} %date{ISO8601} %n + + + + + + + + + + + + + diff --git a/conf/testOnlyDoNotUseInAppConf.routes b/conf/testOnlyDoNotUseInAppConf.routes index 71a2d0e..5bcdd12 100755 --- a/conf/testOnlyDoNotUseInAppConf.routes +++ b/conf/testOnlyDoNotUseInAppConf.routes @@ -10,4 +10,10 @@ # Failing to follow this rule may result in test routes deployed in production. # Add all the application routes to the prod.routes file + +GET /penalties-appeals/income-tax/test-only/feature-switches uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.controllers.FeatureSwitchFrontendController.show() +POST /penalties-appeals/income-tax/test-only/feature-switches uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.frontend.controllers.FeatureSwitchFrontendController.submit() +GET /penalties-appeals/income-tax/test-only/api/feature-switches uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.api.controllers.FeatureSwitchApiController.getFeatureSwitches() ++ nocsrf +POST /penalties-appeals/income-tax/test-only/api/feature-switches uk.gov.hmrc.incometaxpenaltiesappealsfrontend.featureswitch.api.controllers.FeatureSwitchApiController.updateFeatureSwitches() -> / prod.Routes diff --git a/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceControllerISpec.scala b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceControllerISpec.scala new file mode 100644 index 0000000..f9db050 --- /dev/null +++ b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceControllerISpec.scala @@ -0,0 +1,46 @@ +/* + * 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 uk.gov.hmrc.incometaxpenaltiesappealsfrontend.controllers +import org.jsoup.Jsoup +import play.api.http.Status.OK +import uk.gov.hmrc.incometaxpenaltiesappealsfrontend.utils.{ComponentSpecHelper, ViewSpecHelper} + +class ServiceControllerISpec extends ComponentSpecHelper with ViewSpecHelper { + + + + "GET /" should { + + val result = get("/") + val document = Jsoup.parse(result.body) + + "return an OK with a view" in { + + result.status shouldBe OK + + } + + "have the correct page elements" in { + + document.getServiceName.text() shouldBe "Appeal a Self Assessment penalty" + document.title() shouldBe "Appeal a Self Assessment penalty - Appeal a Self Assessment penalty - GOV.UK" + document.getH1Elements.text() shouldBe "Appeal a Self Assessment penalty" + + } + } + +} diff --git a/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/ComponentSpecHelper.scala b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/ComponentSpecHelper.scala new file mode 100644 index 0000000..c00f84a --- /dev/null +++ b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/ComponentSpecHelper.scala @@ -0,0 +1,102 @@ +/* + * Copyright 2022 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.incometaxpenaltiesappealsfrontend.utils + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} +import org.scalatestplus.play.guice.GuiceOneServerPerSuite +import play.api.Application +import play.api.inject.guice.GuiceApplicationBuilder +import play.api.libs.json.Writes +import play.api.libs.ws.{WSClient, WSRequest, WSResponse} +import play.api.test.Helpers._ + +trait ComponentSpecHelper + extends AnyWordSpec + with Matchers + with CustomMatchers + with WiremockHelper + with BeforeAndAfterAll + with BeforeAndAfterEach + with GuiceOneServerPerSuite { + + def extraConfig(): Map[String, String] = Map.empty + + override lazy val app: Application = new GuiceApplicationBuilder() + .configure(config ++ extraConfig()) + .configure("play.http.router" -> "testOnlyDoNotUseInAppConf.Routes") + .build() + + val mockHost: String = WiremockHelper.wiremockHost + val mockPort: String = WiremockHelper.wiremockPort.toString + val mockUrl: String = s"http://$mockHost:$mockPort" + + def config: Map[String, String] = Map( + "microservice.services.des.host" -> mockHost, + "microservice.services.des.port" -> mockPort, + "auditing.enabled" -> "false", + "play.filters.csrf.header.bypassHeaders.Csrf-Token" -> "nocheck" + ) + + implicit val ws: WSClient = app.injector.instanceOf[WSClient] + + override def beforeAll(): Unit = { + startWiremock() + super.beforeAll() + } + + override def afterAll(): Unit = { + stopWiremock() + super.afterAll() + } + + override def beforeEach(): Unit = { + resetWiremock() + super.beforeEach() + } + + def get[T](uri: String): WSResponse = { + await(buildClient(uri).withHttpHeaders("Authorization" -> "Bearer 123").get()) + } + + def post[T](uri: String)(body: T)(implicit writes: Writes[T]): WSResponse = { + await( + buildClient(uri) + .withHttpHeaders("Content-Type" -> "application/json", "Authorization" -> "Bearer 123") + .post(writes.writes(body).toString()) + ) + } + + def put[T](uri: String)(body: T)(implicit writes: Writes[T]): WSResponse = { + await( + buildClient(uri) + .withHttpHeaders("Content-Type" -> "application/json", "Authorization" -> "Bearer 123") + .put(writes.writes(body).toString()) + ) + } + + def delete[T](uri: String): WSResponse = { + await(buildClient(uri).withHttpHeaders("Authorization" -> "Bearer 123").delete()) + } + + val baseUrl: String = "/penalties-appeals/income-tax" + + private def buildClient(path: String): WSRequest = + ws.url(s"http://localhost:$port$baseUrl$path").withFollowRedirects(false) + +} diff --git a/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/CustomMatchers.scala b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/CustomMatchers.scala new file mode 100644 index 0000000..ed1fcd4 --- /dev/null +++ b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/CustomMatchers.scala @@ -0,0 +1,63 @@ +/* + * Copyright 2022 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.incometaxpenaltiesappealsfrontend.utils + +import org.scalatest.matchers.{HavePropertyMatchResult, HavePropertyMatcher} +import play.api.libs.json.Reads +import play.api.libs.ws.WSResponse + +trait CustomMatchers { + def httpStatus(expectedValue: Int): HavePropertyMatcher[WSResponse, Int] = + (response: WSResponse) => + HavePropertyMatchResult( + response.status == expectedValue, + "httpStatus", + expectedValue, + response.status + ) + + def jsonBodyAs[T](expectedValue: T)(implicit reads: Reads[T]): HavePropertyMatcher[WSResponse, T] = + (response: WSResponse) => + HavePropertyMatchResult( + response.json.as[T] == expectedValue, + "jsonBodyAs", + expectedValue, + response.json.as[T] + ) + + val emptyBody: HavePropertyMatcher[WSResponse, String] = + (response: WSResponse) => + HavePropertyMatchResult( + response.body == "", + "emptyBody", + "", + response.body + ) + + def redirectUri(expectedValue: String): HavePropertyMatcher[WSResponse, String] = + (response: WSResponse) => { + val redirectLocation: Option[String] = response.header("Location") + + val matchCondition = redirectLocation.exists(_.contains(expectedValue)) + HavePropertyMatchResult( + matchCondition, + "redirectUri", + expectedValue, + redirectLocation.getOrElse("") + ) + } +} diff --git a/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/ViewSpecHelper.scala b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/ViewSpecHelper.scala new file mode 100644 index 0000000..7834542 --- /dev/null +++ b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/ViewSpecHelper.scala @@ -0,0 +1,117 @@ +/* + * 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 uk.gov.hmrc.incometaxpenaltiesappealsfrontend.utils + +import org.jsoup.nodes.Element +import org.jsoup.select.Elements +import org.scalatest.matchers.{HavePropertyMatchResult, HavePropertyMatcher} + +import scala.jdk.CollectionConverters.IteratorHasAsScala + + +trait ViewSpecHelper { + + implicit class ElementExtensions(element: Element) { + + lazy val content: Element = element.getElementsByTag("article").iterator().asScala.toList.head + + lazy val getParagraphs: Elements = element.getElementsByTag("p") + + lazy val getBulletPoints: Elements = element.getElementsByTag("li") + + lazy val getH1Elements: Elements = element.getElementsByTag("h1") + + lazy val getH2Elements: Elements = element.getElementsByTag("h2") + + lazy val getFormElements: Elements = element.getElementsByClass("form-field-group") + + lazy val getLabelElement: Elements = element.getElementsByTag("label") + + lazy val getLegendElement: Elements = element.getElementsByTag("legend") + + lazy val getErrorSummaryTitle: Elements = element.getElementsByClass("govuk-error-summary__title") + + lazy val getErrorSummaryBody: Elements = element.getElementsByClass("govuk-error-summary__body") + + lazy val getFieldErrorMessage: Elements = element.getElementsByClass("govuk-error-message") + + lazy val getSubmitButton: Elements = element.getElementsByClass("govuk-button") + + lazy val getHintText: String = element.select(s"""span[class=govuk-hint]""").text() + + lazy val getForm: Elements = element.select("form") + + lazy val getSummaryListRows: Elements = element.getElementsByClass("govuk-summary-list__row") + + lazy val getServiceName: Elements = element.getElementsByClass("govuk-header__service-name") + + def getSpan(id: String): Elements = element.select(s"""span[id=$id]""") + + def getLink(id: String): Elements = element.select(s"""a[id=$id]""") + + def getTextFieldInput(id: String): Elements = element.select(s"""input[id=$id]""") + + def getBulletPointList: Elements = element.select("ul[class=list list-bullet]") + + def getDetailsSummary: String = element.getElementsByClass("govuk-details__summary-text").text() + + def getSummaryListQuestion: String = element.getElementsByClass("govuk-summary-list__key").text + + def getSummaryListAnswer: String = element.getElementsByClass("govuk-summary-list__value").text + + def getSummaryListChangeLink: String = element.select("dd.govuk-summary-list__actions > a").attr("href") + + def getSummaryListChangeText: String = element.select("dd.govuk-summary-list__actions > a").text + + def getBanner: Elements = element.getElementsByClass("govuk-phase-banner__text") + + lazy val getSignOutLink: String = element.select(".hmrc-sign-out-nav__link").attr("href") + + lazy val getSignOutText: String = element.select(".hmrc-sign-out-nav__link").text + + lazy val getTechnicalHelpLink: String = element.getElementsByClass("hmrc-report-technical-issue").attr("href") + + lazy val getTechnicalHelpLinkText: String = element.getElementsByClass("hmrc-report-technical-issue").text + + lazy val getBackLinks: Elements = element.getElementsByClass("govuk-back-link") + + lazy val getHints: Elements = element.getElementsByClass("govuk-hint") + } + + def text(text: String): HavePropertyMatcher[Elements, String] = + (element: Elements) => HavePropertyMatchResult( + element.text() == text, + "text", + text, + element.text() + ) + + def findElementByAttrValue(elements: Elements, index: Int, attr: String, value: String): Option[Element] = { + + if(index < elements.size() - 1){ + val element: Element = elements.get(index) + if(element.attr(attr) == value) { + Some(element) + } else { + findElementByAttrValue(elements, index + 1, attr, value) + } + } else { + None + } + } + +} diff --git a/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/WiremockHelper.scala b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/WiremockHelper.scala new file mode 100644 index 0000000..67bbe7c --- /dev/null +++ b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/WiremockHelper.scala @@ -0,0 +1,100 @@ +/* + * Copyright 2022 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.incometaxpenaltiesappealsfrontend.utils + +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock +import com.github.tomakehurst.wiremock.client.WireMock._ +import com.github.tomakehurst.wiremock.core.WireMockConfiguration +import com.github.tomakehurst.wiremock.core.WireMockConfiguration._ +import com.github.tomakehurst.wiremock.stubbing.StubMapping +import org.scalatest.concurrent.{Eventually, IntegrationPatience} + +object WiremockHelper extends Eventually with IntegrationPatience { + + val wiremockPort: Int = 11111 + val wiremockHost: String = "localhost" + + def verifyPost(uri: String, optBody: Option[String] = None): Unit = { + val uriMapping = postRequestedFor(urlEqualTo(uri)) + val postRequest = optBody match { + case Some(body) => uriMapping.withRequestBody(equalTo(body)) + case None => uriMapping + } + verify(postRequest) + } + + def verifyGet(uri: String): Unit = verify(getRequestedFor(urlEqualTo(uri))) + def verifyGet(times: Int, uri: String): Unit = verify(times, getRequestedFor(urlEqualTo(uri))) + + def stubGet(url: String, status: Integer, body: String): StubMapping = + stubFor( + get(urlMatching(url)) + .willReturn( + aResponse().withStatus(status).withBody(body) + ) + ) + + def stubPost(url: String, status: Integer, responseBody: String): StubMapping = + stubFor( + post(urlMatching(url)) + .willReturn( + aResponse().withStatus(status).withBody(responseBody) + ) + ) + + def stubPut(url: String, status: Integer, responseBody: String): StubMapping = + stubFor( + put(urlMatching(url)) + .willReturn( + aResponse().withStatus(status).withBody(responseBody) + ) + ) + + def stubPatch(url: String, status: Integer, responseBody: String): StubMapping = + stubFor( + patch(urlMatching(url)) + .willReturn( + aResponse().withStatus(status).withBody(responseBody) + ) + ) + + def stubDelete(url: String, status: Integer, responseBody: String): StubMapping = + stubFor( + delete(urlMatching(url)) + .willReturn( + aResponse().withStatus(status).withBody(responseBody) + ) + ) +} + +trait WiremockHelper { + + import WiremockHelper._ + + lazy val wmConfig: WireMockConfiguration = wireMockConfig().port(wiremockPort) + lazy val wireMockServer: WireMockServer = new WireMockServer(wmConfig) + + def startWiremock(): Unit = { + wireMockServer.start() + WireMock.configureFor(wiremockHost, wiremockPort) + } + + def stopWiremock(): Unit = wireMockServer.stop() + + def resetWiremock(): Unit = WireMock.reset() +} diff --git a/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/WiremockMethods.scala b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/WiremockMethods.scala new file mode 100644 index 0000000..9ffeb4e --- /dev/null +++ b/it/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/utils/WiremockMethods.scala @@ -0,0 +1,105 @@ +/* + * Copyright 2022 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.incometaxpenaltiesappealsfrontend.utils + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock._ +import com.github.tomakehurst.wiremock.matching.UrlPattern +import com.github.tomakehurst.wiremock.stubbing.StubMapping +import play.api.libs.json.Writes + +trait WiremockMethods { + def when[T](method: HTTPMethod, uri: String, body: T)(implicit writes: Writes[T]): Mapping = { + when(method, uri, Map.empty, body) + } + + def when(method: HTTPMethod, uri: String, headers: Map[String, String] = Map.empty): Mapping = { + new Mapping(method, uri, headers, None) + } + + def when[T](method: HTTPMethod, uri: String, headers: Map[String, String], body: T)( + implicit writes: Writes[T] + ): Mapping = { + val stringBody = writes.writes(body).toString() + new Mapping(method, uri, headers, Some(stringBody)) + } + class Mapping( + method: HTTPMethod, + uri: String, + headers: Map[String, String], + body: Option[String] + ) { + private val mapping = { + val uriMapping = method.wireMockMapping(urlMatching(uri)) + + val uriMappingWithHeaders = headers.foldLeft(uriMapping) { + case (m, (key, value)) => m.withHeader(key, equalTo(value)) + } + + body match { + case Some(extractedBody) => uriMappingWithHeaders.withRequestBody(equalToJson(extractedBody)) + case None => uriMappingWithHeaders + } + } + + def thenReturn[T](status: Int, body: T)(implicit writes: Writes[T]): StubMapping = { + val stringBody = writes.writes(body).toString() + thenReturnInternal(status, Map.empty, Some(stringBody)) + } + + def thenReturn[T](status: Int, headers: Map[String, String], body: T)(implicit writes: Writes[T]): StubMapping = { + val stringBody = writes.writes(body).toString() + thenReturnInternal(status, headers, Some(stringBody)) + } + + def thenReturn(status: Int, headers: Map[String, String] = Map.empty): StubMapping = { + thenReturnInternal(status, headers, None) + } + + private def thenReturnInternal(status: Int, headers: Map[String, String], body: Option[String]): StubMapping = { + val response = { + val statusResponse = aResponse().withStatus(status) + val responseWithHeaders = headers.foldLeft(statusResponse) { + case (res, (key, value)) => res.withHeader(key, value) + } + body match { + case Some(extractedBody) => responseWithHeaders.withBody(extractedBody) + case None => responseWithHeaders + } + } + + stubFor(mapping.willReturn(response)) + } + } + + sealed trait HTTPMethod { + val wireMockMapping: UrlPattern => MappingBuilder + } + + case object GET extends HTTPMethod { + override val wireMockMapping: UrlPattern => MappingBuilder = get + } + + case object POST extends HTTPMethod { + override val wireMockMapping: UrlPattern => MappingBuilder = post + } + + case object PUT extends HTTPMethod { + override val wireMockMapping: UrlPattern => MappingBuilder = put + } + +} diff --git a/project/AppDependencies.scala b/project/AppDependencies.scala index 2b75653..2b3b04e 100755 --- a/project/AppDependencies.scala +++ b/project/AppDependencies.scala @@ -7,7 +7,7 @@ object AppDependencies { val compile: Seq[ModuleID] = Seq( "uk.gov.hmrc" %% "bootstrap-frontend-play-30" % bootstrapVersion, - "uk.gov.hmrc" %% "play-frontend-hmrc-play-30" % "11.3.0" + "uk.gov.hmrc" %% "play-frontend-hmrc-play-30" % "11.5.0" ) val test: Seq[ModuleID] = Seq( diff --git a/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/HelloWorldControllerSpec.scala b/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceControllerSpec.scala similarity index 89% rename from test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/HelloWorldControllerSpec.scala rename to test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceControllerSpec.scala index 97113b8..9c8ff08 100755 --- a/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/HelloWorldControllerSpec.scala +++ b/test/uk/gov/hmrc/incometaxpenaltiesappealsfrontend/controllers/ServiceControllerSpec.scala @@ -25,14 +25,14 @@ import play.api.test.FakeRequest import play.api.test.Helpers._ import play.api.inject.guice.GuiceApplicationBuilder -class HelloWorldControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppPerSuite { +class ServiceControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppPerSuite { override def fakeApplication(): Application = new GuiceApplicationBuilder() .build() private val fakeRequest = FakeRequest("GET", "/") - private val controller = app.injector.instanceOf[HelloWorldController] + private val controller = app.injector.instanceOf[ServiceController] "GET /" should { "return 200" in {