Skip to content

Commit

Permalink
Merge pull request #255 from hmrc/ITSASU-2626
Browse files Browse the repository at this point in the history
ITSASU-2626 - Update to remove usage of cred-id, replaced with option…
  • Loading branch information
MandeepSanghaHMRC authored Oct 13, 2023
2 parents 317c780 + 2046452 commit f07daea
Show file tree
Hide file tree
Showing 15 changed files with 336 additions and 186 deletions.
4 changes: 4 additions & 0 deletions app/Module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@

import com.google.inject.AbstractModule
import config.AppConfig
import repositories.{LockoutMongoRepository, SubscriptionDataRepository, ThrottlingRepository}

class Module extends AbstractModule {

override def configure(): Unit = {
bind(classOf[AppConfig]).to(classOf[config.MicroserviceAppConfig]).asEagerSingleton()
bind(classOf[SubscriptionDataRepository]).asEagerSingleton()
bind(classOf[LockoutMongoRepository]).asEagerSingleton()
bind(classOf[ThrottlingRepository]).asEagerSingleton()
}

}
36 changes: 16 additions & 20 deletions app/controllers/SubscriptionDataController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package controllers

import common.Extractors
import play.api.Logging
import play.api.libs.json.{JsError, JsSuccess, JsValue, Json}
import play.api.mvc.{Action, AnyContent, ControllerComponents}
Expand All @@ -30,31 +31,26 @@ import scala.concurrent.{ExecutionContext, Future}
class SubscriptionDataController @Inject()(authService: AuthService,
subscriptionDataService: SubscriptionDataService,
cc: ControllerComponents)
(implicit ec: ExecutionContext) extends BackendController(cc) with Logging {
(implicit ec: ExecutionContext) extends BackendController(cc) with Logging with Extractors {

def retrieveReference: Action[JsValue] = Action.async(parse.json) { implicit request =>
authService.authorised().retrieve(Retrievals.credentials) {
case Some(credentials) =>
(request.body \ "utr").validate[String] match {
case JsSuccess(utr, _) =>
subscriptionDataService.retrieveReference(utr, credentials.providerId) map{
case SubscriptionDataService.Existing(reference) => Ok(Json.obj("reference" -> reference))
case SubscriptionDataService.Created(reference) => Created(Json.obj("reference" -> reference))
}
case JsError(_) =>
logger.error("[SubscriptionDataController][retrieveReference] - Could not parse json request.")
Future.successful(InternalServerError(
s"[SubscriptionDataController][retrieveReference] - Could not parse json request."
))
}
case None =>
logger.error("[SubscriptionDataController][retrieveReference] - Could not retrieve users credentials.")
Future.successful(InternalServerError(
"[SubscriptionDataController][retrieveReference] - Could not retrieve users credentials."
))
authService.authorised().retrieve(Retrievals.allEnrolments) { enrolments =>
(request.body \ "utr").validate[String] match {
case JsSuccess(utr, _) =>
subscriptionDataService.retrieveReference(utr, getArnFromEnrolments(enrolments)) map {
case SubscriptionDataService.Existing(reference) => Ok(Json.obj("reference" -> reference))
case SubscriptionDataService.Created(reference) => Created(Json.obj("reference" -> reference))
}
case JsError(_) =>
logger.error("[SubscriptionDataController][retrieveReference] - Could not parse json request.")
Future.successful(InternalServerError(
s"[SubscriptionDataController][retrieveReference] - Could not parse json request."
))
}
}
}


def getAllSubscriptionData(reference: String): Action[AnyContent] = Action.async { implicit request =>
authService.authorised() {
subscriptionDataService.getAllSubscriptionData(reference).map {
Expand Down
69 changes: 44 additions & 25 deletions app/repositories/SubscriptionDataRepository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import org.bson.Document
import org.bson.conversions.Bson
import org.mongodb.scala.model.IndexModel
import org.mongodb.scala.result.InsertOneResult
import org.mongodb.scala.{Observable, SingleObservable}
import play.api.libs.json.Json.JsValueWrapper
import play.api.libs.json.{Format, JsObject, JsValue, Json}
import play.api.libs.json._
import uk.gov.hmrc.http.InternalServerException
import uk.gov.hmrc.mongo.MongoComponent
import uk.gov.hmrc.mongo.play.json.PlayMongoRepository
import utils.JsonUtils.JsObjectUtil

import java.time.Instant
import java.util.UUID
Expand All @@ -37,6 +37,7 @@ import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}
import scala.language.implicitConversions


@Singleton
class SubscriptionDataRepositoryConfig @Inject()(val appConfig: AppConfig) {

Expand All @@ -47,7 +48,7 @@ class SubscriptionDataRepositoryConfig @Inject()(val appConfig: AppConfig) {
def mongoComponent: MongoComponent = MongoComponent(appConfig.mongoUri)

def indexes: Seq[IndexModel] =
Seq(ttlIndex(ttlLengthSeconds), utrCredIndex, referenceIndex)
Seq(ttlIndex(ttlLengthSeconds), utrArnIndex, referenceIndex)

}

Expand All @@ -58,7 +59,7 @@ class SubscriptionDataRepository @Inject()(config: SubscriptionDataRepositoryCon
mongoComponent = config.mongoComponent,
domainFormat = implicitly[Format[JsObject]],
indexes = config.indexes,
replaceIndexes = false
replaceIndexes = true
) {

private val findOneAndUpdateOptions: FindOneAndUpdateOptions = new FindOneAndUpdateOptions().upsert(true)
Expand All @@ -67,10 +68,11 @@ class SubscriptionDataRepository @Inject()(config: SubscriptionDataRepositoryCon

private val removeIdProjection = toBson(Json.obj(_Id -> 0))

private def find(selector: JsObject, projection: Option[JsObject]): Future[Seq[JsValue]] = {
def find(selector: JsObject, projection: Option[JsObject]): Future[Seq[JsValue]] = {
collection
.find(selector)
.projection(projection.map(toBson).getOrElse(removeIdProjection))
.toFuture()
}

def getReferenceData(reference: String): Future[Option[JsValue]] = {
Expand Down Expand Up @@ -109,8 +111,15 @@ class SubscriptionDataRepository @Inject()(config: SubscriptionDataRepositoryCon
remove("reference" -> Json.toJson(reference))
}

def retrieveReference(utr: String, credId: String): Future[Option[String]] = {
val selector: JsObject = Json.obj("utr" -> utr, "credId" -> credId)
def retrieveReference(utr: String, maybeARN: Option[String]): Future[Option[String]] = {
val arnSelector: JsValue = maybeARN match {
case Some(value) => JsString(value)
case None => Json.obj("$exists" -> false)
}
val selector: JsObject = Json.obj(
"utr" -> utr,
"arn" -> arnSelector
)
val projection = Json.obj(_Id -> 0)
find(selector, Some(projection)).map {
_.headOption map { json =>
Expand All @@ -124,32 +133,46 @@ class SubscriptionDataRepository @Inject()(config: SubscriptionDataRepositoryCon
}
}

def createReference(utr: String, credId: String, sessionId: String): Future[String] = {
def createReference(utr: String, maybeARN: Option[String]): Future[String] = {
val arn: Option[JsObject] = maybeARN map { value => Json.obj("arn" -> value) }
val reference: String = UUID.randomUUID().toString
val document: JsObject = Json.obj(
"utr" -> utr,
"credId" -> credId,
"reference" -> reference,
"sessionId" -> sessionId,
"lastUpdatedTimestamp" -> Json.obj(
"$date" -> Instant.now.toEpochMilli
).as[JsValue]
)
) ++ arn
insert(document).map { result =>
if (result.wasAcknowledged()) reference
else throw new InternalServerException("[SubscriptionDataRepository][createReference] - Unable to create document reference")
}
}

private def findAndUpdate(selector: JsObject, update: JsObject, fetchNewObject: Boolean = false, upsert: Boolean = false) =
collection.findOneAndUpdate(selector, update, findOneAndUpdateOptions).toFuture().map(asOption)
private def findAndUpdate(selector: JsObject, update: JsObject, fetchNewObject: Boolean = false, upsert: Boolean = false) = {
collection
.findOneAndUpdate(selector, update, findOneAndUpdateOptions)
.toFuture()
.map(asOption)
}

private def remove(tuples: (String, JsValueWrapper)*) =
collection.deleteOne(Json.obj(tuples: _*))
private def remove(tuples: (String, JsValueWrapper)*) = {
collection
.deleteOne(Json.obj(tuples: _*))
.toFuture()
}

def insert(document: JsObject): Future[InsertOneResult] = collection.insertOne(document).toFuture()
def insert(document: JsObject): Future[InsertOneResult] = {
collection
.insertOne(document)
.toFuture()
}

def drop(): Future[Void] = collection.drop().toFuture()
def drop(): Future[Void] = {
collection
.drop()
.toFuture()
}

}

Expand All @@ -162,20 +185,16 @@ object SubscriptionDataRepository {
def descending: Int = -1
}

implicit def asOption(o: JsObject): Option[JsValue] = o.result.toOption.flatMap(Option(_))
def asOption(o: JsObject): Option[JsValue] = o.result.toOption.flatMap(Option(_))

implicit def toBson(doc: JsObject): Bson = Document.parse(doc.toString())

implicit def toFuture[T](observable: SingleObservable[T]): Future[T] = observable.toFuture()

implicit def toFuture[T](observable: Observable[T]): Future[Seq[T]] = observable.toFuture()

val lastUpdatedTimestampKey = "lastUpdatedTimestamp"

val utrCredIndex: IndexModel = new IndexModel(
Json.obj("utr" -> IndexType.ascending, "credId" -> IndexType.ascending),
val utrArnIndex: IndexModel = new IndexModel(
Json.obj("utr" -> IndexType.ascending, "arn" -> IndexType.ascending),
new IndexOptions()
.name("utrCredIndex")
.name("utrArnIndex")
.unique(true)
.sparse(true)
)
Expand Down
14 changes: 3 additions & 11 deletions app/services/SubscriptionDataService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import config.featureswitch.FeatureSwitching
import play.api.libs.json.JsValue
import repositories.SubscriptionDataRepository
import services.SubscriptionDataService.{Created, Existence, Existing}
import uk.gov.hmrc.http.{HeaderCarrier, InternalServerException}

import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}
Expand All @@ -31,17 +30,10 @@ import scala.concurrent.{ExecutionContext, Future}
class SubscriptionDataService @Inject()(subscriptionDataRepository: SubscriptionDataRepository, val appConfig: AppConfig)
(implicit ec: ExecutionContext) extends FeatureSwitching {


private[services] def sessionIdFromHC(implicit hc: HeaderCarrier): String = {
hc.sessionId.fold(
throw new InternalServerException("[SubscriptionDataService][retrieveSelfEmployments] - No session id in header carrier")
)(_.value)
}

def retrieveReference(utr: String, credId: String)(implicit hc: HeaderCarrier): Future[Existence] = {
subscriptionDataRepository.retrieveReference(utr, credId) flatMap {
def retrieveReference(utr: String, arn: Option[String]): Future[Existence] = {
subscriptionDataRepository.retrieveReference(utr, arn) flatMap {
case Some(value) => Future.successful(Existing(value))
case None => subscriptionDataRepository.createReference(utr, credId, sessionIdFromHC) map (reference => Created(reference))
case None => subscriptionDataRepository.createReference(utr, arn) map (reference => Created(reference))
}
}

Expand Down
44 changes: 44 additions & 0 deletions app/testonly/controllers/RemoveDataController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 testonly.controllers

import play.api.libs.json.Json
import play.api.mvc.{Action, AnyContent, ControllerComponents}
import repositories.SubscriptionDataRepository
import uk.gov.hmrc.play.bootstrap.backend.controller.BackendController

import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}

@Singleton
class RemoveDataController @Inject()(subscriptionDataRepository: SubscriptionDataRepository,
cc: ControllerComponents)
(implicit ec: ExecutionContext) extends BackendController(cc) {

def removeData(utr: String): Action[AnyContent] = Action.async { _ =>
subscriptionDataRepository.find(Json.obj("utr" -> utr), None) flatMap { found =>
Future.sequence(
found
.map(value => (value \ "reference").as[String])
.map(subscriptionDataRepository.deleteDataFromReference)
)
} map { _ =>
Ok
}
}

}
3 changes: 3 additions & 0 deletions conf/testOnlyDoNotUseInAppConf.routes
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@


GET /income-tax-subscription/client-matching/test-only/reset-agent-lockout testonly.controllers.matching.ResetAgentLockoutController.resetAgentLockout

GET /income-tax-subscription/test-only/feature-switch testonly.controllers.featureswitch.FeatureSwitchController.get
POST /income-tax-subscription/test-only/feature-switch testonly.controllers.featureswitch.FeatureSwitchController.update

GET /income-tax-subscription/test-only/throttle testonly.controllers.throttle.ThrottleController.get
POST /income-tax-subscription/test-only/throttle testonly.controllers.throttle.ThrottleController.update

DELETE /income-tax-subscription/test-only/remove-data/:utr testonly.controllers.RemoveDataController.removeData(utr: String)

# Add all the application routes to the prod.routes file
-> / prod.Routes
4 changes: 2 additions & 2 deletions it/controllers/BusinessIncomeSourcesControllerISpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class BusinessIncomeSourcesControllerISpec extends ComponentSpecBase with Featur
"POST /mis/create/mtditid" should {
s"return a $NO_CONTENT response" when {
"income sources are successfully submitted" in {
AuthStub.stubAuth(OK, Json.obj())
AuthStub.stubAuth(OK)
stubAuditing()
CreateIncomeSourceStub.stub(testMtdbsaRef, Json.toJson(testCreateIncomeSources), appConfig.desAuthorisationToken, appConfig.desEnvironment)(
OK, testCreateIncomeSuccessBody
Expand All @@ -156,7 +156,7 @@ class BusinessIncomeSourcesControllerISpec extends ComponentSpecBase with Featur
}
s"return a $INTERNAL_SERVER_ERROR" when {
"the submission of income sources failed" in {
AuthStub.stubAuth(OK, Json.obj())
AuthStub.stubAuth(OK)
stubAuditing()
CreateIncomeSourceStub.stub(testMtdbsaRef, Json.toJson(testCreateIncomeSources), appConfig.desAuthorisationToken, appConfig.desEnvironment)(
INTERNAL_SERVER_ERROR, testCreateIncomeFailureBody
Expand Down
8 changes: 4 additions & 4 deletions it/controllers/MandationStatusControllerISpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MandationStatusControllerISpec extends ComponentSpecBase {
TaxYearStatus(AccountingPeriodUtil.getCurrentTaxYear.toItsaStatusShortTaxYear, Voluntary),
TaxYearStatus(AccountingPeriodUtil.getNextTaxYear.toItsaStatusShortTaxYear, Voluntary)
)
AuthStub.stubAuth(OK, Json.obj())
AuthStub.stubAuth(OK)
GetItsaStatusStub.stub(
"test-nino", "test-utr", AccountingPeriodUtil.getCurrentTaxYear.toItsaStatusShortTaxYear
)(OK, Json.toJson(expectedResponse))
Expand All @@ -55,7 +55,7 @@ class MandationStatusControllerISpec extends ComponentSpecBase {
"return BAD_REQUEST" when {
"the request body is invalid" in {
Given("I setup the Wiremock stubs")
AuthStub.stubAuth(OK, Json.obj())
AuthStub.stubAuth(OK)

When("POST /itsa-status is called")
val response = IncomeTaxSubscription.mandationStatus(Json.obj("invalid" -> "request"))
Expand All @@ -70,7 +70,7 @@ class MandationStatusControllerISpec extends ComponentSpecBase {
"return INTERNAL_SERVER_ERROR" when {
"the status-determination-service returns OK status and invalid JSON" in {
Given("I setup the Wiremock stubs")
AuthStub.stubAuth(OK, Json.obj())
AuthStub.stubAuth(OK)
GetItsaStatusStub.stubInvalidResponse(
"test-nino", "test-utr", AccountingPeriodUtil.getCurrentTaxYear.toItsaStatusShortTaxYear
)(OK, "{ currentYearStatus")
Expand All @@ -86,7 +86,7 @@ class MandationStatusControllerISpec extends ComponentSpecBase {

"the status-determination-service returns INTERNAL_SERVER_ERROR" in {
Given("I setup the Wiremock stubs")
AuthStub.stubAuth(OK, Json.obj())
AuthStub.stubAuth(OK)
val failedResponse = Json.obj(
"failures" -> Json.arr(
Json.obj(
Expand Down
4 changes: 2 additions & 2 deletions it/controllers/SignUpControllerISpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class SignUpControllerISpec extends ComponentSpecBase with FeatureSwitching {

"signUp" should {
"call sign up connector successfully when auth succeeds for a sign up submission 200" in {
AuthStub.stubAuth(OK, Json.obj())
AuthStub.stubAuth(OK)
SignUpStub.stubSignUp(testNino, testSignUpSubmission(testNino), appConfig.desAuthorisationToken, appConfig.desEnvironment)(
OK, testSignUpSuccessBody
)
Expand All @@ -41,7 +41,7 @@ class SignUpControllerISpec extends ComponentSpecBase with FeatureSwitching {

"feature switch is enabled call sign up connector successfully when auth succeeds for a sign up submission 200" in {
enable(TaxYearSignup)
AuthStub.stubAuth(OK, Json.obj())
AuthStub.stubAuth(OK)
SignUpTaxYearStub.stubSignUp(
testTaxYearSignUpSubmission(testNino, testTaxYear),
appConfig.signUpServiceAuthorisationToken,
Expand Down
Loading

0 comments on commit f07daea

Please sign in to comment.