diff --git a/app/Module.scala b/app/Module.scala index 586b2a5e..d888ddd2 100644 --- a/app/Module.scala +++ b/app/Module.scala @@ -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() } } diff --git a/app/controllers/SubscriptionDataController.scala b/app/controllers/SubscriptionDataController.scala index 04060fda..12c6213b 100644 --- a/app/controllers/SubscriptionDataController.scala +++ b/app/controllers/SubscriptionDataController.scala @@ -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} @@ -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 { diff --git a/app/repositories/SubscriptionDataRepository.scala b/app/repositories/SubscriptionDataRepository.scala index e3bf523f..26b042b6 100644 --- a/app/repositories/SubscriptionDataRepository.scala +++ b/app/repositories/SubscriptionDataRepository.scala @@ -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 @@ -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) { @@ -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) } @@ -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) @@ -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]] = { @@ -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 => @@ -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() + } } @@ -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) ) diff --git a/app/services/SubscriptionDataService.scala b/app/services/SubscriptionDataService.scala index 8d41a548..09c2dce2 100644 --- a/app/services/SubscriptionDataService.scala +++ b/app/services/SubscriptionDataService.scala @@ -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} @@ -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)) } } diff --git a/app/testonly/controllers/RemoveDataController.scala b/app/testonly/controllers/RemoveDataController.scala new file mode 100644 index 00000000..20116193 --- /dev/null +++ b/app/testonly/controllers/RemoveDataController.scala @@ -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 + } + } + +} diff --git a/conf/testOnlyDoNotUseInAppConf.routes b/conf/testOnlyDoNotUseInAppConf.routes index bf535c5b..692081ba 100644 --- a/conf/testOnlyDoNotUseInAppConf.routes +++ b/conf/testOnlyDoNotUseInAppConf.routes @@ -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 diff --git a/it/controllers/BusinessIncomeSourcesControllerISpec.scala b/it/controllers/BusinessIncomeSourcesControllerISpec.scala index 5ce6eb4e..b05a8084 100644 --- a/it/controllers/BusinessIncomeSourcesControllerISpec.scala +++ b/it/controllers/BusinessIncomeSourcesControllerISpec.scala @@ -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 @@ -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 diff --git a/it/controllers/MandationStatusControllerISpec.scala b/it/controllers/MandationStatusControllerISpec.scala index 4d6c60bb..3e786aa0 100644 --- a/it/controllers/MandationStatusControllerISpec.scala +++ b/it/controllers/MandationStatusControllerISpec.scala @@ -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)) @@ -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")) @@ -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") @@ -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( diff --git a/it/controllers/SignUpControllerISpec.scala b/it/controllers/SignUpControllerISpec.scala index 28bc76b7..903b88dd 100644 --- a/it/controllers/SignUpControllerISpec.scala +++ b/it/controllers/SignUpControllerISpec.scala @@ -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 ) @@ -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, diff --git a/it/controllers/SubscriptionDataControllerISpec.scala b/it/controllers/SubscriptionDataControllerISpec.scala index 42a43a0d..5d4e566a 100644 --- a/it/controllers/SubscriptionDataControllerISpec.scala +++ b/it/controllers/SubscriptionDataControllerISpec.scala @@ -19,6 +19,7 @@ package controllers import config.AppConfig import config.featureswitch.FeatureSwitching import helpers.ComponentSpecBase +import helpers.IntegrationTestConstants.testArn import helpers.servicemocks.AuthStub import play.api.http.Status._ import play.api.libs.json.{JsObject, Json} @@ -61,41 +62,71 @@ class SubscriptionDataControllerISpec extends ComponentSpecBase with FeatureSwit super.beforeEach() } - s"POST ${controllers.routes.SubscriptionDataController.retrieveReference.url}" should { - "return OK with a reference" when { - "it already exists in the database" in { - AuthStub.stubAuthSuccess() - await(repository.insert(Json.obj( - "reference" -> reference, - "utr" -> utr, - "credId" -> "test-cred-id" - ))) - - IncomeTaxSubscription.postRetrieveReference(utr) should have( - httpStatus(OK), - jsonBodyOf(Json.obj( - "reference" -> reference - )) - ) + s"POST ${controllers.routes.SubscriptionDataController.retrieveReference.url}" when { + "used by an individual without an arn" should { + "return OK with a reference" when { + "it already exists in the database" in { + AuthStub.stubAuthSuccess() + await(repository.insert(Json.obj( + "reference" -> reference, + "utr" -> utr + ))) + + IncomeTaxSubscription.postRetrieveReference(utr) should have( + httpStatus(OK), + jsonBodyOf(Json.obj( + "reference" -> reference + )) + ) + } } - } - "return CREATED with a reference" when { - "it is not already exists in the database" in { - AuthStub.stubAuthSuccess() - - IncomeTaxSubscription.postRetrieveReference(utr) should have( - httpStatus(CREATED), - jsonBodyContainsField("reference") - ) + "return Created with a reference" when { + "it doesn't already exist in the database" in { + AuthStub.stubAuthSuccess() + + IncomeTaxSubscription.postRetrieveReference(utr) should have( + httpStatus(CREATED), + jsonBodyContainsField("reference") + ) + } } } - "return UNAUTHORISED" when { - "the user is not authorised" in { - AuthStub.stubAuthFailure() - - IncomeTaxSubscription.postUnauthorisedRetrieveReference(utr) should have( - httpStatus(UNAUTHORIZED) - ) + "used by an agent with an arn" should { + "return OK with a reference" when { + "it already exists in the database" in { + AuthStub.stubAgentAuthSuccess() + await(repository.insert(Json.obj( + "reference" -> reference, + "utr" -> utr, + "arn" -> testArn + ))) + + IncomeTaxSubscription.postRetrieveReference(utr) should have( + httpStatus(OK), + jsonBodyOf(Json.obj( + "reference" -> reference + )) + ) + } + } + "return CREATED with a reference" when { + "it doesn't already exist in the database" in { + AuthStub.stubAgentAuthSuccess() + + IncomeTaxSubscription.postRetrieveReference(utr) should have( + httpStatus(CREATED), + jsonBodyContainsField("reference") + ) + } + } + "return UNAUTHORISED" when { + "the user is not authorised" in { + AuthStub.stubAuthFailure() + + IncomeTaxSubscription.postUnauthorisedRetrieveReference(utr) should have( + httpStatus(UNAUTHORIZED) + ) + } } } } diff --git a/it/helpers/servicemocks/AuthStub.scala b/it/helpers/servicemocks/AuthStub.scala index 2d706e87..de83cf45 100644 --- a/it/helpers/servicemocks/AuthStub.scala +++ b/it/helpers/servicemocks/AuthStub.scala @@ -38,11 +38,16 @@ object AuthStub extends WireMockMethods { .thenReturn(status = OK, body = successfulAuthResponse) } - def stubAuth[T](status: Int, body: T): StubMapping = { + def stubAuth(status: Int): StubMapping = { when(method = POST, uri = authority) .thenReturn(status = status, body = successfulAuthResponse(arnEnrolment)) } + def stubAgentAuthSuccess(): StubMapping = { + when(method = POST, uri = authority) + .thenReturn(status = OK, body = successfulAuthResponse(arnEnrolment)) + } + private val arnEnrolment = Json.obj( "key" -> agentServiceEnrolmentName, "identifiers" -> Json.arr( @@ -58,7 +63,7 @@ object AuthStub extends WireMockMethods { Json.obj( "allEnrolments" -> enrolments ) - + private def exceptionHeaders(value: String) = Map(HeaderNames.WWW_AUTHENTICATE -> s"""MDTP detail="$value"""") def stubAuthFailure(): StubMapping = { @@ -70,11 +75,7 @@ object AuthStub extends WireMockMethods { val successfulAuthResponse: JsObject = { Json.obj( - "internalId" -> userIDs.internalId, - "optionalCredentials" -> Json.obj( - "providerId" -> testCredId, - "providerType" -> "GovernmentGateway" - ) + "allEnrolments" -> Json.arr() ) } diff --git a/it/repositories/SubscriptionDataRepositorySpec.scala b/it/repositories/SubscriptionDataRepositorySpec.scala index 2305fe8a..db09f8a9 100644 --- a/it/repositories/SubscriptionDataRepositorySpec.scala +++ b/it/repositories/SubscriptionDataRepositorySpec.scala @@ -16,6 +16,7 @@ package repositories +import helpers.IntegrationTestConstants.testArn import org.scalatest.concurrent.ScalaFutures.convertScalaFuture import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpecLike @@ -195,38 +196,90 @@ class SubscriptionDataRepositorySpec extends AnyWordSpecLike with Matchers with } } - "createReference" should { - "create a document in the database with the details required for its use" in { - val createdReference: String = await(testSelfEmploymentsRepository.createReference(utr, credId, testSessionId)) + "createReference" when { + "provided with both a utr and arn" should { + "create a document in the database with the details required for its use" in { + val createdReference: String = await(testSelfEmploymentsRepository.createReference(utr, Some(testArn))) - val optionalData: Option[JsValue] = testSelfEmploymentsRepository.getReferenceData(createdReference).futureValue - optionalData.isDefined shouldBe true - val data: JsValue = optionalData.get + val optionalData: Option[JsValue] = testSelfEmploymentsRepository.getReferenceData(createdReference).futureValue + optionalData.isDefined shouldBe true + + val data: JsValue = optionalData.get + + (data \ "utr").asOpt[String] shouldBe Some(utr) + (data \ "arn").asOpt[String] shouldBe Some(testArn) + (data \ "reference").asOpt[String] shouldBe Some(createdReference) + (data \ "lastUpdatedTimestamp").isDefined shouldBe true + } + "return an error if a document with the same utr, arn combo is already in the database" in { + await(testSelfEmploymentsRepository.createReference(utr, Some(testArn))) - (data \ "utr").asOpt[String] shouldBe Some(utr) - (data \ "credId").asOpt[String] shouldBe Some(credId) - (data \ "sessionId").asOpt[String] shouldBe Some(testSessionId) - (data \ "reference").asOpt[String] shouldBe Some(createdReference) - (data \ "lastUpdatedTimestamp").isDefined shouldBe true + intercept[Exception](await(testSelfEmploymentsRepository.createReference(utr, Some(testArn)))) + } } - "return an error if a document with the same utr, cred id combo is already in the database" in { - await(testSelfEmploymentsRepository.createReference(utr, credId, testSessionId)) + "provided with only a utr and no arn" should { + "create a document in the database with the details required for its use" in { + val createdReference: String = await(testSelfEmploymentsRepository.createReference(utr, None)) + + val optionalData: Option[JsValue] = testSelfEmploymentsRepository.getReferenceData(createdReference).futureValue + optionalData.isDefined shouldBe true + + val data: JsValue = optionalData.get - intercept[Exception](await(testSelfEmploymentsRepository.createReference(utr, credId, "test-session-id-2"))) + (data \ "utr").asOpt[String] shouldBe Some(utr) + (data \ "arn").asOpt[String] shouldBe None + (data \ "reference").asOpt[String] shouldBe Some(createdReference) + (data \ "lastUpdatedTimestamp").isDefined shouldBe true + } + "return an error if a document with the same utr only is already in the database" in { + await(testSelfEmploymentsRepository.createReference(utr, None)) + + intercept[Exception](await(testSelfEmploymentsRepository.createReference(utr, None))) + } } } - "retrieveReference" should { - "return a reference" when { - "the related utr + cred id exists in the database" in { - val createdReference: String = await(testSelfEmploymentsRepository.createReference(utr, credId, testSessionId)) + "retrieveReference" when { + "provided with only a utr" should { + "return a reference" when { + "the related document with only a utr exists in the database" in { + val createdReference: String = await(testSelfEmploymentsRepository.createReference(utr, None)) - testSelfEmploymentsRepository.retrieveReference(utr, credId).futureValue shouldBe Some(createdReference) + testSelfEmploymentsRepository.retrieveReference(utr, None).futureValue shouldBe Some(createdReference) + } + } + "return no reference" when { + "the related utr without arn does not exist in the database" when { + "no documents exist in the database" in { + testSelfEmploymentsRepository.retrieveReference(utr, None).futureValue shouldBe None + } + "a document with the utr specified, but also an arn exists in the database" in { + await(testSelfEmploymentsRepository.createReference(utr, Some(testArn))) + + testSelfEmploymentsRepository.retrieveReference(utr, None).futureValue shouldBe None + } + } } } - "return no reference" when { - "the related utr + cred id does not exist in the database" in { - testSelfEmploymentsRepository.retrieveReference(utr, credId).futureValue shouldBe None + "provided with a utr and arn" should { + "return a reference" when { + "the related utr + arn exists in the database" in { + val createdReference: String = await(testSelfEmploymentsRepository.createReference(utr, Some(testArn))) + + testSelfEmploymentsRepository.retrieveReference(utr, Some(testArn)).futureValue shouldBe Some(createdReference) + } + } + "return no reference" when { + "the related utr + arn does not exist in the database" when { + "no documents exist in the database" in { + testSelfEmploymentsRepository.retrieveReference(utr, Some(testArn)).futureValue shouldBe None + } + "a document with only the utr exists in the database" in { + await(testSelfEmploymentsRepository.createReference(utr, None)) + + testSelfEmploymentsRepository.retrieveReference(utr, Some(testArn)).futureValue shouldBe None + } + } } } } diff --git a/test/controllers/SubscriptionDataControllerSpec.scala b/test/controllers/SubscriptionDataControllerSpec.scala index 96a5de9e..21eb960e 100644 --- a/test/controllers/SubscriptionDataControllerSpec.scala +++ b/test/controllers/SubscriptionDataControllerSpec.scala @@ -24,7 +24,7 @@ import play.api.test.FakeRequest import play.api.test.Helpers._ import services.SubscriptionDataService.{Created, Existing} import services.mocks.{MockAuthService, MockSubscriptionDataService} -import uk.gov.hmrc.auth.core.retrieve.Credentials +import uk.gov.hmrc.auth.core.{Enrolment, EnrolmentIdentifier, Enrolments} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future @@ -40,41 +40,47 @@ class SubscriptionDataControllerSpec extends CommonSpec with MockSubscriptionDat val request: FakeRequest[AnyContentAsEmpty.type] = FakeRequest() - "retrieveReference" should { - "return an InternalServerError" when { - "the user has no credentials" in { - mockRetrievalSuccess[Option[Credentials]](None) + "retrieveReference" when { + "the user has an arn enrolment" should { + "return Ok with a reference" when { + "SubscriptionDataService returns an Existing reference" in { + mockRetrievalSuccess[Enrolments](Enrolments(Set(Enrolment("HMRC-AS-AGENT", Seq(EnrolmentIdentifier("AgentReferenceNumber", testArn)), "Activated")))) + mockRetrieveReference("1234567890", Some(testArn), Existing)(reference) - val result = TestController.retrieveReference()(request.withBody(Json.obj())) + val result = TestController.retrieveReference()(request.withBody(Json.obj("utr" -> "1234567890"))) - status(result) shouldBe INTERNAL_SERVER_ERROR + status(result) shouldBe OK + } } - "an invalid payload is received" in { - mockRetrievalSuccess[Option[Credentials]](Some(Credentials("test-cred-id", "ggProvider"))) + "return Created with a reference" when { + "SubscriptionDataService returns an Created reference" in { + mockRetrievalSuccess[Enrolments](Enrolments(Set(Enrolment("HMRC-AS-AGENT", Seq(EnrolmentIdentifier("AgentReferenceNumber", testArn)), "Activated")))) + mockRetrieveReference("1234567890", Some(testArn), Created)(reference) - val result = TestController.retrieveReference()(request.withBody(Json.obj())) + val result = TestController.retrieveReference()(request.withBody(Json.obj("utr" -> "1234567890"))) - status(result) shouldBe INTERNAL_SERVER_ERROR + status(result) shouldBe CREATED + } } } - "return Ok with a reference" when { - "SubscriptionDataService returns an Existing reference" in { - mockRetrievalSuccess[Option[Credentials]](Some(Credentials("test-cred-id", "ggProvider"))) - mockRetrieveReference("1234567890", Existing, "test-cred-id")(reference) + "the user does not have an arn enrolment" should { + "return Ok with a reference" when { + "SubscriptionDataService returns an Existing reference" in { + mockRetrievalSuccess[Enrolments](Enrolments(Set())) + mockRetrieveReference("1234567890", None, Existing)(reference) - val result = TestController.retrieveReference()(request.withBody(Json.obj("utr" -> "1234567890"))) + val result = TestController.retrieveReference()(request.withBody(Json.obj("utr" -> "1234567890"))) - status(result) shouldBe OK - } - } - "return Created with a reference" when { - "SubscriptionDataService returns an Created reference" in { - mockRetrievalSuccess[Option[Credentials]](Some(Credentials("test-cred-id", "ggProvider"))) - mockRetrieveReference("1234567890", Created, "test-cred-id")(reference) + status(result) shouldBe OK + } + "SubscriptionDataService returns an Created reference" in { + mockRetrievalSuccess[Enrolments](Enrolments(Set())) + mockRetrieveReference("1234567890", None, Created)(reference) - val result = TestController.retrieveReference()(request.withBody(Json.obj("utr" -> "1234567890"))) + val result = TestController.retrieveReference()(request.withBody(Json.obj("utr" -> "1234567890"))) - status(result) shouldBe CREATED + status(result) shouldBe CREATED + } } } } diff --git a/test/services/SubscriptionDataServiceSpec.scala b/test/services/SubscriptionDataServiceSpec.scala index a71067c2..415252b0 100644 --- a/test/services/SubscriptionDataServiceSpec.scala +++ b/test/services/SubscriptionDataServiceSpec.scala @@ -29,7 +29,6 @@ import play.api.libs.json.{JsObject, Json} import play.api.test.Helpers.{await, defaultAwaitTimeout} import repositories.SubscriptionDataRepository import services.SubscriptionDataService.{Created, Existence, Existing} -import uk.gov.hmrc.http.{HeaderCarrier, InternalServerException, SessionId} import uk.gov.hmrc.play.bootstrap.config.ServicesConfig import scala.concurrent.ExecutionContext.Implicits.global @@ -53,56 +52,58 @@ class SubscriptionDataServiceSpec extends CommonSpec with MockitoSugar with Feat val testDataId: String = "dataId" val reference: String = "test-reference" val utr: String = "1234567890" - val credId: String = "test-cred-id" - - "sessionIdFromHC" should { - "return the sessionId" when { - "there is one sessionId value" in new Setup { - implicit val hc: HeaderCarrier = HeaderCarrier(sessionId = Some(SessionId(testSessionId))) - val result: String = service.sessionIdFromHC(hc) - result shouldBe testSessionId - } - } - - "throw internal server exception" when { - "the sessionId is not in the headerCarrier" in new Setup { - implicit val hc: HeaderCarrier = HeaderCarrier() + val arn: String = "1" - val result: InternalServerException = intercept[InternalServerException](service.sessionIdFromHC(hc)) - result.message shouldBe "[SubscriptionDataService][retrieveSelfEmployments] - No session id in header carrier" - } - } + "retrieveReference" when { + "arn is not provided" should { + "create a reference in the database" when { + "it doesn't exist in the database" in new Setup { + when(mockSubscriptionDataRepository.retrieveReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(None))) + .thenReturn(Future.successful(None)) + when(mockSubscriptionDataRepository.createReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(None))) + .thenReturn(Future.successful(reference)) - } + val result: Existence = await(service.retrieveReference(utr, None)) - "retrieveReference" should { - "return the reference from the database" when { - "it exists in the database" in new Setup { - implicit val hc: HeaderCarrier = HeaderCarrier(sessionId = Some(SessionId(testSessionId))) + result shouldBe Created(reference) + } + } - when(mockSubscriptionDataRepository.retrieveReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(credId))) - .thenReturn(Future.successful(Some(reference))) + "return the reference from the database" when { + "it exists in the database" in new Setup { + when(mockSubscriptionDataRepository.retrieveReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(None))) + .thenReturn(Future.successful(Some(reference))) - val result: Existence = await(service.retrieveReference(utr, credId)) + val result: Existence = await(service.retrieveReference(utr, None)) - result shouldBe Existing(reference) + result shouldBe Existing(reference) + } } } - "createReference" should { + "arn is provided" should { "create a reference in the database" when { "it doesn't exist in the database" in new Setup { - implicit val hc: HeaderCarrier = HeaderCarrier(sessionId = Some(SessionId(testSessionId))) - - when(mockSubscriptionDataRepository.retrieveReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(credId))) + when(mockSubscriptionDataRepository.retrieveReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(Some(arn)))) .thenReturn(Future.successful(None)) - when(mockSubscriptionDataRepository.createReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(credId), ArgumentMatchers.eq(testSessionId))) + when(mockSubscriptionDataRepository.createReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(Some(arn)))) .thenReturn(Future.successful(reference)) - val result: Existence = await(service.retrieveReference(utr, credId)) + val result: Existence = await(service.retrieveReference(utr, Some(arn))) result shouldBe Created(reference) } } + + "return the reference from the database" when { + "it exists in the database" in new Setup { + when(mockSubscriptionDataRepository.retrieveReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(Some(arn)))) + .thenReturn(Future.successful(Some(reference))) + + val result: Existence = await(service.retrieveReference(utr, Some(arn))) + + result shouldBe Existing(reference) + } + } } } diff --git a/test/services/mocks/MockSubscriptionDataService.scala b/test/services/mocks/MockSubscriptionDataService.scala index 7ca7dd66..fabf7a65 100644 --- a/test/services/mocks/MockSubscriptionDataService.scala +++ b/test/services/mocks/MockSubscriptionDataService.scala @@ -38,8 +38,8 @@ trait MockSubscriptionDataService extends BeforeAndAfterEach with MockitoSugar { reset(mockSubscriptionDataService) } - def mockRetrieveReference(utr: String, existence: String => SubscriptionDataService.Existence, credId: String)(result: String): Unit = { - when(mockSubscriptionDataService.retrieveReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(credId))(ArgumentMatchers.any())) + def mockRetrieveReference(utr: String, arn: Option[String], existence: String => SubscriptionDataService.Existence)(result: String): Unit = { + when(mockSubscriptionDataService.retrieveReference(ArgumentMatchers.eq(utr), ArgumentMatchers.eq(arn))) .thenReturn(Future.successful(existence(result))) }