diff --git a/app/uk/gov/hmrc/apisubscriptionfields/connector/JsonFormatters.scala b/app/uk/gov/hmrc/apisubscriptionfields/connector/JsonFormatters.scala index f6d728e..02f31c4 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/connector/JsonFormatters.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/connector/JsonFormatters.scala @@ -17,12 +17,10 @@ package uk.gov.hmrc.apisubscriptionfields.connector import play.api.libs.json._ -import uk.gov.hmrc.apiplatform.modules.common.domain.models._ import uk.gov.hmrc.apisubscriptionfields.model.{BoxId, SubscriptionFieldsId} trait JsonFormatters { - implicit val clientIdJF: Format[ClientId] = Json.valueFormat[ClientId] implicit val boxIdJF: Format[BoxId] = Json.valueFormat[BoxId] implicit val subscriptionFieldsIdJF: Format[SubscriptionFieldsId] = Json.valueFormat[SubscriptionFieldsId] diff --git a/app/uk/gov/hmrc/apisubscriptionfields/connector/PushPullNotificationServiceConnector.scala b/app/uk/gov/hmrc/apisubscriptionfields/connector/PushPullNotificationServiceConnector.scala index 95808f7..64788f0 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/connector/PushPullNotificationServiceConnector.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/connector/PushPullNotificationServiceConnector.scala @@ -60,13 +60,15 @@ class PushPullNotificationServiceConnector @Inject() (http: HttpClient, appConfi } } - def updateCallBackUrl(clientId: ClientId, boxId: BoxId, callbackUrl: FieldValue)(implicit hc: HeaderCarrier): Future[PPNSCallBackUrlValidationResponse] = { + def updateCallBackUrl(clientId: ClientId, boxId: BoxId, callbackUrl: FieldValue)(implicit hc: HeaderCarrier): Future[Either[String, Unit]] = { val payload = UpdateCallBackUrlRequest(clientId, callbackUrl) http .PUT[UpdateCallBackUrlRequest, UpdateCallBackUrlResponse](s"$externalServiceUri/box/${boxId.value.toString}/callback", payload) .map(response => - if (response.successful) PPNSCallBackUrlSuccessResponse - else response.errorMessage.fold(PPNSCallBackUrlFailedResponse("Unknown Error"))(PPNSCallBackUrlFailedResponse) + if (response.successful) + Right(()) + else + Left(response.errorMessage.getOrElse("Unknown Error")) ) .recover { case NonFatal(e) => throw new RuntimeException(s"Unexpected response from $externalServiceUri: ${e.getMessage}") diff --git a/app/uk/gov/hmrc/apisubscriptionfields/controller/SubscriptionFieldsController.scala b/app/uk/gov/hmrc/apisubscriptionfields/controller/SubscriptionFieldsController.scala index 622a6fd..1643033 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/controller/SubscriptionFieldsController.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/controller/SubscriptionFieldsController.scala @@ -92,11 +92,9 @@ class SubscriptionFieldsController @Inject() (cc: ControllerComponents, service: .upsert(clientId, apiContext, apiVersionNbr, payload.fields) .map(_ match { case NotFoundSubsFieldsUpsertResponse => BadRequest(Json.toJson("reason" -> "field definitions not found")) // TODO - case FailedValidationSubsFieldsUpsertResponse(fieldErrorMessages) => - BadRequest(Json.toJson(fieldErrorMessages)) + case FailedValidationSubsFieldsUpsertResponse(fieldErrorMessages) => BadRequest(Json.toJson(fieldErrorMessages)) case SuccessfulSubsFieldsUpsertResponse(response, true) => Created(Json.toJson(response)) - case SuccessfulSubsFieldsUpsertResponse(response, false) => - Ok(Json.toJson(response)) + case SuccessfulSubsFieldsUpsertResponse(response, false) => Ok(Json.toJson(response)) }) .recover(recovery) } diff --git a/app/uk/gov/hmrc/apisubscriptionfields/model/Topics.scala b/app/uk/gov/hmrc/apisubscriptionfields/model/BoxId.scala similarity index 100% rename from app/uk/gov/hmrc/apisubscriptionfields/model/Topics.scala rename to app/uk/gov/hmrc/apisubscriptionfields/model/BoxId.scala diff --git a/app/uk/gov/hmrc/apisubscriptionfields/model/Responses.scala b/app/uk/gov/hmrc/apisubscriptionfields/model/Responses.scala index 52f3ba4..35edeb6 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/model/Responses.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/model/Responses.scala @@ -35,10 +35,6 @@ sealed trait SubsFieldValidationResponse case object ValidSubsFieldValidationResponse extends SubsFieldValidationResponse case class InvalidSubsFieldValidationResponse(errorResponses: Map[FieldName, String]) extends SubsFieldValidationResponse -sealed trait PPNSCallBackUrlValidationResponse -case object PPNSCallBackUrlSuccessResponse extends PPNSCallBackUrlValidationResponse -case class PPNSCallBackUrlFailedResponse(errorMsg: String) extends PPNSCallBackUrlValidationResponse - sealed trait ErrorCode object ErrorCode { diff --git a/app/uk/gov/hmrc/apisubscriptionfields/service/PushPullNotificationService.scala b/app/uk/gov/hmrc/apisubscriptionfields/service/PushPullNotificationService.scala index 7c70345..238d1f4 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/service/PushPullNotificationService.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/service/PushPullNotificationService.scala @@ -17,39 +17,28 @@ package uk.gov.hmrc.apisubscriptionfields.service import javax.inject.{Inject, Singleton} -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.Future import uk.gov.hmrc.apiplatform.modules.common.domain.models._ import uk.gov.hmrc.http.HeaderCarrier import uk.gov.hmrc.apisubscriptionfields.connector.PushPullNotificationServiceConnector -import uk.gov.hmrc.apisubscriptionfields.model.Types.FieldValue -import uk.gov.hmrc.apisubscriptionfields.model.{FieldDefinition, _} +import uk.gov.hmrc.apisubscriptionfields.model.BoxId +import uk.gov.hmrc.apisubscriptionfields.model.Types.{FieldName, FieldValue} @Singleton -class PushPullNotificationService @Inject() (ppnsConnector: PushPullNotificationServiceConnector)(implicit ec: ExecutionContext) { +class PushPullNotificationService @Inject() (ppnsConnector: PushPullNotificationServiceConnector) { - def makeBoxName(apiContext: ApiContext, apiVersionNbr: ApiVersionNbr, fieldDefinition: FieldDefinition): String = { + def makeBoxName(apiContext: ApiContext, apiVersionNbr: ApiVersionNbr, fieldName: FieldName): String = { val separator = "##" - s"${apiContext.value}${separator}${apiVersionNbr.value}${separator}${fieldDefinition.name.value}" + s"${apiContext.value}${separator}${apiVersionNbr.value}${separator}${fieldName.value}" } - def subscribeToPPNS( - clientId: ClientId, - apiContext: ApiContext, - apiVersionNbr: ApiVersionNbr, - oFieldValue: Option[FieldValue], - fieldDefinition: FieldDefinition - )(implicit - hc: HeaderCarrier - ): Future[PPNSCallBackUrlValidationResponse] = { - for { - boxId <- ppnsConnector.ensureBoxIsCreated(makeBoxName(apiContext, apiVersionNbr, fieldDefinition), clientId) - result <- oFieldValue match { - case Some(value) => ppnsConnector.updateCallBackUrl(clientId, boxId, value) - case None => Future.successful(PPNSCallBackUrlSuccessResponse) - } - } yield result + def ensureBoxIsCreated(clientId: ClientId, apiContext: ApiContext, apiVersionNbr: ApiVersionNbr, fieldName: FieldName)(implicit hc: HeaderCarrier): Future[BoxId] = { + ppnsConnector.ensureBoxIsCreated(makeBoxName(apiContext, apiVersionNbr, fieldName), clientId) } + def updateCallbackUrl(clientId: ClientId, boxId: BoxId, fieldValue: FieldValue)(implicit hc: HeaderCarrier): Future[Either[String, Unit]] = { + ppnsConnector.updateCallBackUrl(clientId, boxId, fieldValue) + } } diff --git a/app/uk/gov/hmrc/apisubscriptionfields/service/SubscriptionFieldsService.scala b/app/uk/gov/hmrc/apisubscriptionfields/service/SubscriptionFieldsService.scala index 1d1203e..f3d6a16 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/service/SubscriptionFieldsService.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/service/SubscriptionFieldsService.scala @@ -22,8 +22,10 @@ import scala.concurrent.{ExecutionContext, Future} import cats.data.NonEmptyList import cats.data.{NonEmptyList => NEL} +import cats.implicits._ import uk.gov.hmrc.apiplatform.modules.common.domain.models._ +import uk.gov.hmrc.apiplatform.modules.common.services.EitherTHelper import uk.gov.hmrc.http.HeaderCarrier import uk.gov.hmrc.apisubscriptionfields.model.Types._ @@ -32,89 +34,116 @@ import uk.gov.hmrc.apisubscriptionfields.repository.SubscriptionFieldsRepository @Singleton class SubscriptionFieldsService @Inject() ( - repository: SubscriptionFieldsRepository, + subscriptionFieldsRepository: SubscriptionFieldsRepository, apiFieldDefinitionsService: ApiFieldDefinitionsService, pushPullNotificationService: PushPullNotificationService )(implicit ec: ExecutionContext ) { - private def validate(fields: Fields, fieldDefinitions: NonEmptyList[FieldDefinition]): SubsFieldValidationResponse = { - SubscriptionFieldsService.validateAgainstValidationRules(fieldDefinitions, fields) ++ SubscriptionFieldsService.validateFieldNamesAreDefined(fieldDefinitions, fields) match { - case FieldErrorMap.empty => ValidSubsFieldValidationResponse - case errs: FieldErrorMap => InvalidSubsFieldValidationResponse(errorResponses = errs) + def upsert(clientId: ClientId, apiContext: ApiContext, apiVersionNbr: ApiVersionNbr, newFields: Fields)(implicit hc: HeaderCarrier): Future[SubsFieldsUpsertResponse] = { + def findPpnsField(fieldDefinitions: NEL[FieldDefinition]): Option[FieldDefinition] = fieldDefinitions.find(_.`type` == FieldDefinitionType.PPNS_FIELD) + + def handleAnyPpnsSubscriptionRequired( + clientId: ClientId, + apiContext: ApiContext, + apiVersionNbr: ApiVersionNbr, + fieldDefinitions: NEL[FieldDefinition], + existingFields: Option[SubscriptionFields] + )(implicit + hc: HeaderCarrier + ): Future[Either[String, Unit]] = { + + findPpnsField(fieldDefinitions) match { + case Some(fieldDefinition) => + val fieldName = fieldDefinition.name + newFields.get(fieldName) match { + case Some(newFieldValue) => + for { + boxId <- pushPullNotificationService.ensureBoxIsCreated(clientId, apiContext, apiVersionNbr, fieldName) + fieldValueHasNotChanged = existingFields.map(_.fields.get(fieldName).contains(newFieldValue)).getOrElse(false) + result <- if (fieldValueHasNotChanged) successful(Right(())) + else pushPullNotificationService.updateCallbackUrl(clientId, boxId, newFieldValue) + } yield result + case None => successful(Right(())) + } + case None => successful(Right(())) + } } - } - private def upsertSubscriptionFields(clientId: ClientId, apiContext: ApiContext, apiVersionNbr: ApiVersionNbr, fields: Fields): Future[SuccessfulSubsFieldsUpsertResponse] = { - repository - .saveAtomic(clientId, apiContext, apiVersionNbr, fields) - .map(result => SuccessfulSubsFieldsUpsertResponse(result._1, result._2)) - } + def validateFields(fields: Fields, fieldDefinitions: NonEmptyList[FieldDefinition]): Either[FieldErrorMap, Unit] = { + SubscriptionFieldsService.validateAgainstValidationRules(fieldDefinitions, fields) ++ SubscriptionFieldsService.validateFieldNamesAreDefined(fieldDefinitions, fields) match { + case FieldErrorMap.empty => Right(()) + case errs: FieldErrorMap => Left(errs) + } + } + + def translateValidateError(fieldErrorMessages: FieldErrorMap) = FailedValidationSubsFieldsUpsertResponse(fieldErrorMessages) - def handlePPNS( - clientId: ClientId, - apiContext: ApiContext, - apiVersionNbr: ApiVersionNbr, - fieldDefinitions: NEL[FieldDefinition], - fields: Fields - )(implicit - hc: HeaderCarrier - ): Future[SubsFieldsUpsertResponse] = { - val ppnsFieldDefinition: Option[FieldDefinition] = fieldDefinitions.find(_.`type` == FieldDefinitionType.PPNS_FIELD) - - ppnsFieldDefinition match { - case Some(fieldDefinition) => - val oFieldValue: Option[FieldValue] = fields.get(fieldDefinition.name) - pushPullNotificationService.subscribeToPPNS(clientId, apiContext, apiVersionNbr, oFieldValue, fieldDefinition).flatMap { - case PPNSCallBackUrlSuccessResponse => upsertSubscriptionFields(clientId, apiContext, apiVersionNbr, fields) - case PPNSCallBackUrlFailedResponse(error) => Future.successful(FailedValidationSubsFieldsUpsertResponse(Map(fieldDefinition.name -> error))) - } - case None => upsertSubscriptionFields(clientId, apiContext, apiVersionNbr, fields) + def translatePpnsError(fieldDefinitions: NEL[FieldDefinition])(error: String) = { + val fieldName = findPpnsField(fieldDefinitions).get.name + val fieldErrorMessages = Map(fieldName -> error) + FailedValidationSubsFieldsUpsertResponse(fieldErrorMessages) } - } + def upsertIfFieldsHaveChanged(anyExistingFields: Option[SubscriptionFields]) = { + def upsertSubscriptionFields(clientId: ClientId, apiContext: ApiContext, apiVersionNbr: ApiVersionNbr, fields: Fields): Future[SuccessfulSubsFieldsUpsertResponse] = { + subscriptionFieldsRepository + .saveAtomic(clientId, apiContext, apiVersionNbr, fields) + .map(result => SuccessfulSubsFieldsUpsertResponse(result._1, result._2)) + } + + anyExistingFields match { + case Some(existingFields) if (existingFields.fields == newFields) => + successful(SuccessfulSubsFieldsUpsertResponse(existingFields, false)) + case _ => + upsertSubscriptionFields(clientId, apiContext, apiVersionNbr, newFields) + } + } - def upsert(clientId: ClientId, apiContext: ApiContext, apiVersionNbr: ApiVersionNbr, fields: Fields)(implicit hc: HeaderCarrier): Future[SubsFieldsUpsertResponse] = { - val foFieldDefinitions: Future[Option[NonEmptyList[FieldDefinition]]] = - apiFieldDefinitionsService.get(apiContext, apiVersionNbr).map(_.map(_.fieldDefinitions)) - - get(clientId, apiContext, apiVersionNbr).flatMap(_ match { - case Some(sfields) if (sfields.fields == fields) => Future.successful(SuccessfulSubsFieldsUpsertResponse(sfields, false)) - case _ => - foFieldDefinitions.flatMap(_ match { - case None => successful(NotFoundSubsFieldsUpsertResponse) - case Some(fieldDefinitions) => - validate(fields, fieldDefinitions) match { - case ValidSubsFieldValidationResponse => handlePPNS(clientId, apiContext, apiVersionNbr, fieldDefinitions, fields) - case InvalidSubsFieldValidationResponse(fieldErrorMessages) => successful(FailedValidationSubsFieldsUpsertResponse(fieldErrorMessages)) - } - }) - }) + (for { + anyFieldDefinitions <- apiFieldDefinitionsService.get(apiContext, apiVersionNbr).map(_.map(_.fieldDefinitions)) + anyExistingFields <- subscriptionFieldsRepository.fetch(clientId, apiContext, apiVersionNbr) + } yield (anyFieldDefinitions, anyExistingFields)) + .flatMap(_ match { + case (None, Some(existingFields)) if (existingFields.fields == newFields) => successful(SuccessfulSubsFieldsUpsertResponse(existingFields, false)) + case (None, _) => successful(NotFoundSubsFieldsUpsertResponse) + case (Some(fieldDefinitions), anyExistingFields) => + val E = EitherTHelper.make[FailedValidationSubsFieldsUpsertResponse] + ( + for { + _ <- E.fromEither(validateFields(newFields, fieldDefinitions).leftMap(translateValidateError)) + _ <- E.fromEitherF(handleAnyPpnsSubscriptionRequired(clientId, apiContext, apiVersionNbr, fieldDefinitions, anyExistingFields) + .map(_.leftMap(translatePpnsError(fieldDefinitions)))) + response <- E.liftF(upsertIfFieldsHaveChanged(anyExistingFields)) + } yield response + ) + .merge + }) } def delete(clientId: ClientId, apiContext: ApiContext, apiVersionNbr: ApiVersionNbr): Future[Boolean] = { - repository.delete(clientId, apiContext, apiVersionNbr) + subscriptionFieldsRepository.delete(clientId, apiContext, apiVersionNbr) } def delete(clientId: ClientId): Future[Boolean] = { - repository.delete(clientId) + subscriptionFieldsRepository.delete(clientId) } def get(clientId: ClientId, apiContext: ApiContext, apiVersionNbr: ApiVersionNbr): Future[Option[SubscriptionFields]] = { for { - fetch <- repository.fetch(clientId, apiContext, apiVersionNbr) + fetch <- subscriptionFieldsRepository.fetch(clientId, apiContext, apiVersionNbr) } yield fetch } def getBySubscriptionFieldId(subscriptionFieldsId: SubscriptionFieldsId): Future[Option[SubscriptionFields]] = { for { - fetch <- repository.fetchByFieldsId(subscriptionFieldsId) + fetch <- subscriptionFieldsRepository.fetchByFieldsId(subscriptionFieldsId) } yield fetch } def getByClientId(clientId: ClientId): Future[Option[BulkSubscriptionFieldsResponse]] = { (for { - fields <- repository.fetchByClientId(clientId) + fields <- subscriptionFieldsRepository.fetchByClientId(clientId) } yield fields) .map { case Nil => None @@ -124,7 +153,7 @@ class SubscriptionFieldsService @Inject() ( def getAll: Future[BulkSubscriptionFieldsResponse] = { (for { - fields <- repository.fetchAll + fields <- subscriptionFieldsRepository.fetchAll } yield fields) .map(BulkSubscriptionFieldsResponse(_)) } diff --git a/conf/application.conf b/conf/application.conf index 008c744..5a4d6db 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -75,6 +75,8 @@ controllers { # You can disable evolutions if needed # evolutionplugin=disabled +# Microservice specific config + mongo-async-driver { akka { loglevel = WARNING diff --git a/conf/logback.xml b/conf/logback.xml index 4aaecb2..7cf8b1f 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -45,7 +45,7 @@ - + diff --git a/test/uk/gov/hmrc/apisubscriptionfields/FieldDefinitionTestData.scala b/test/uk/gov/hmrc/apisubscriptionfields/FieldDefinitionTestData.scala index 888a24f..07b7307 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/FieldDefinitionTestData.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/FieldDefinitionTestData.scala @@ -80,7 +80,7 @@ trait FieldDefinitionTestData extends TestData { FieldDefinition("password", "password", "this is your password", FieldDefinitionType.SECURE_TOKEN, "password", Some(FakeValidationForPassword)) final val FakeFieldDefinitionPPNSFields = - FieldDefinition("callbackurl", "callbackurl", "please enter a callback url", FieldDefinitionType.PPNS_FIELD, "callbackurl", Some(FakeValidationForPPNS)) + FieldDefinition(PPNSFieldFieldName, "Callback URL", "please enter a callback url", FieldDefinitionType.PPNS_FIELD, "callback", Some(FakeValidationForPPNS)) final val FakeApiFieldDefinitionssWithRegex = NonEmptyList.fromListUnsafe(List(FakeFieldDefinitionAlphnumericField, FakeFieldDefinitionPassword)) final val FakeApiFieldDefinitionsPPNSWithRegex = diff --git a/test/uk/gov/hmrc/apisubscriptionfields/SubscriptionFieldsTestData.scala b/test/uk/gov/hmrc/apisubscriptionfields/SubscriptionFieldsTestData.scala index 8101ab3..41c4d10 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/SubscriptionFieldsTestData.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/SubscriptionFieldsTestData.scala @@ -35,8 +35,10 @@ trait SubscriptionFieldsTestData extends FieldDefinitionTestData with Validation final val SubscriptionFieldsMatchRegexValidation: Fields = Map(AlphanumericFieldName -> "ABC123ab", PasswordFieldName -> "Qw12@ert") final val SubscriptionFieldsNonMatchRegexValidation: Fields = Map(AlphanumericFieldName -> "ABC123a", PasswordFieldName -> "Qw12@er") + final val PPNSFieldFieldValue: FieldValue = "https://www.mycallbackurl.com" + final val SubscriptionFieldsMatchRegexValidationPPNS: Fields = - Map(AlphanumericFieldName -> "ABC123abc", PasswordFieldName -> "Qw12@erty", PPNSFieldFieldName -> "https://www.mycallbackurl.com") + Map(AlphanumericFieldName -> "ABC123abc", PasswordFieldName -> "Qw12@erty", PPNSFieldFieldName -> PPNSFieldFieldValue) final val SubscriptionFieldsDoNotMatchRegexValidationPPNS: Fields = Map(AlphanumericFieldName -> "ABC123abc", PasswordFieldName -> "Qw12@erty", PPNSFieldFieldName -> "foo") final val SubscriptionFieldsEmptyValueRegexValidationPPNS: Fields = Map(AlphanumericFieldName -> "ABC123abc", PasswordFieldName -> "Qw12@erty", PPNSFieldFieldName -> "") final val SubscriptionFieldsDoNotMatchRegexValidation: Fields = Map(AlphanumericFieldName -> "ABC123abc=", PasswordFieldName -> "Qw12erty") diff --git a/test/uk/gov/hmrc/apisubscriptionfields/TestData.scala b/test/uk/gov/hmrc/apisubscriptionfields/TestData.scala index 22c731c..c5f4885 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/TestData.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/TestData.scala @@ -22,6 +22,8 @@ import play.api.http.HeaderNames.{ACCEPT, CONTENT_TYPE} import play.api.http.MimeTypes import uk.gov.hmrc.apiplatform.modules.common.domain.models._ +import uk.gov.hmrc.apisubscriptionfields.model.BoxId + trait TestData { type EmulatedFailure = UnsupportedOperationException @@ -42,6 +44,7 @@ trait TestData { final val FakeClientId2 = ClientId(fakeRawClientId2) + final val FakeBoxId = BoxId(UUID.randomUUID()) } object RequestHeaders { diff --git a/test/uk/gov/hmrc/apisubscriptionfields/connector/PushPullNotificationServiceConnectorSpec.scala b/test/uk/gov/hmrc/apisubscriptionfields/connector/PushPullNotificationServiceConnectorSpec.scala index 1ab9418..0b79651 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/connector/PushPullNotificationServiceConnectorSpec.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/connector/PushPullNotificationServiceConnectorSpec.scala @@ -173,8 +173,8 @@ class PushPullNotificationServiceConnectorSpec extends AsyncHmrcSpec with GuiceO val path = s"/box/${boxId.value}/callback" primeStub(path, requestBody, responseBody) - val ret: PPNSCallBackUrlValidationResponse = await(connector.updateCallBackUrl(clientId, boxId, callbackUrl)) - ret shouldBe PPNSCallBackUrlSuccessResponse + val ret = await(connector.updateCallBackUrl(clientId, boxId, callbackUrl)) + ret shouldBe Right(()) verifyPath(path) } @@ -187,8 +187,8 @@ class PushPullNotificationServiceConnectorSpec extends AsyncHmrcSpec with GuiceO val path = s"/box/${boxId.value}/callback" primeStub(path, requestBody, responseBody) - val ret: PPNSCallBackUrlValidationResponse = await(connector.updateCallBackUrl(clientId, boxId, callbackUrl)) - ret shouldBe PPNSCallBackUrlSuccessResponse + val ret = await(connector.updateCallBackUrl(clientId, boxId, callbackUrl)) + ret shouldBe Right(()) verifyPath(path) } @@ -201,8 +201,8 @@ class PushPullNotificationServiceConnectorSpec extends AsyncHmrcSpec with GuiceO val path = s"/box/${boxId.value}/callback" primeStub(path, requestBody, responseBody) - val ret: PPNSCallBackUrlValidationResponse = await(connector.updateCallBackUrl(clientId, boxId, callbackUrl)) - ret shouldBe PPNSCallBackUrlFailedResponse("some error") + val ret = await(connector.updateCallBackUrl(clientId, boxId, callbackUrl)) + ret shouldBe Left("some error") verifyPath(path) } @@ -227,8 +227,8 @@ class PushPullNotificationServiceConnectorSpec extends AsyncHmrcSpec with GuiceO val path = s"/box/${boxId.value}/callback" primeStub(path, requestBody, responseBody) - val ret: PPNSCallBackUrlValidationResponse = await(connector.updateCallBackUrl(clientId, boxId, callbackUrl)) - ret shouldBe PPNSCallBackUrlFailedResponse("Unknown Error") + val ret = await(connector.updateCallBackUrl(clientId, boxId, callbackUrl)) + ret shouldBe Left("Unknown Error") verifyPath(path) } diff --git a/test/uk/gov/hmrc/apisubscriptionfields/mocks/ApiFieldDefinitionsServiceMockModule.scala b/test/uk/gov/hmrc/apisubscriptionfields/mocks/ApiFieldDefinitionsServiceMockModule.scala new file mode 100644 index 0000000..daa3622 --- /dev/null +++ b/test/uk/gov/hmrc/apisubscriptionfields/mocks/ApiFieldDefinitionsServiceMockModule.scala @@ -0,0 +1,49 @@ +/* + * 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.apisubscriptionfields.mocks + +import scala.concurrent.Future.successful + +import org.mockito.{ArgumentMatchersSugar, MockitoSugar} + +import uk.gov.hmrc.apiplatform.modules.common.domain.models.{ApiContext, ApiVersionNbr} +import uk.gov.hmrc.apiplatform.modules.common.utils.FixedClock + +import uk.gov.hmrc.apisubscriptionfields.model.ApiFieldDefinitions +import uk.gov.hmrc.apisubscriptionfields.service.ApiFieldDefinitionsService + +trait ApiFieldDefinitionsServiceMockModule extends MockitoSugar with ArgumentMatchersSugar with FixedClock { + + protected trait BaseApiFieldDefinitionsServiceMock { + def aMock: ApiFieldDefinitionsService + + def verifyZeroInteractions(): Unit = MockitoSugar.verifyZeroInteractions(aMock) + + object Get { + + def returns(context: ApiContext, version: ApiVersionNbr, response: ApiFieldDefinitions) = + when(aMock.get(eqTo(context), eqTo(version))).thenReturn(successful(Some(response))) + + def returnsNothing(context: ApiContext, version: ApiVersionNbr) = + when(aMock.get(eqTo(context), eqTo(version))).thenReturn(successful(None)) + } + } + + object ApiFieldDefinitionsServiceMock extends BaseApiFieldDefinitionsServiceMock { + val aMock = mock[ApiFieldDefinitionsService] + } +} diff --git a/test/uk/gov/hmrc/apisubscriptionfields/mocks/FieldDefinitionRepositoryMockModule.scala b/test/uk/gov/hmrc/apisubscriptionfields/mocks/FieldDefinitionRepositoryMockModule.scala new file mode 100644 index 0000000..6f3b49e --- /dev/null +++ b/test/uk/gov/hmrc/apisubscriptionfields/mocks/FieldDefinitionRepositoryMockModule.scala @@ -0,0 +1,51 @@ +/* + * 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.apisubscriptionfields.mocks + +import scala.concurrent.Future.successful + +import org.mockito.{ArgumentMatchersSugar, MockitoSugar} + +import uk.gov.hmrc.apiplatform.modules.common.domain.models.{ApiContext, ApiVersionNbr} +import uk.gov.hmrc.apiplatform.modules.common.utils.FixedClock + +import uk.gov.hmrc.apisubscriptionfields.model.ApiFieldDefinitions +import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert +import uk.gov.hmrc.apisubscriptionfields.repository.ApiFieldDefinitionsRepository + +trait FieldDefinitionRepositoryMockModule extends MockitoSugar with ArgumentMatchersSugar with FixedClock { + + protected trait BaseFieldDefinitionRepositoryMock { + def aMock: ApiFieldDefinitionsRepository + + object Save { + + def returns(definitions: ApiFieldDefinitions, ret: (ApiFieldDefinitions, IsInsert)) = + when(aMock.save(eqTo(definitions))).thenReturn(successful(ret)) + } + + object Fetch { + + def returns(apiContext: ApiContext, apiVersion: ApiVersionNbr, ret: Option[ApiFieldDefinitions]) = + when(aMock.fetch(eqTo(apiContext), eqTo(apiVersion))).thenReturn(successful(ret)) + } + } + + object FieldDefinitionRepositoryMock extends BaseFieldDefinitionRepositoryMock { + val aMock = mock[ApiFieldDefinitionsRepository] + } +} diff --git a/test/uk/gov/hmrc/apisubscriptionfields/mocks/PushPullNotificationServiceMockModule.scala b/test/uk/gov/hmrc/apisubscriptionfields/mocks/PushPullNotificationServiceMockModule.scala new file mode 100644 index 0000000..23ffe6d --- /dev/null +++ b/test/uk/gov/hmrc/apisubscriptionfields/mocks/PushPullNotificationServiceMockModule.scala @@ -0,0 +1,60 @@ +/* + * 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.apisubscriptionfields.mocks + +import scala.concurrent.Future.{failed, successful} + +import org.mockito.{ArgumentMatchersSugar, MockitoSugar, Strictness} + +import uk.gov.hmrc.apiplatform.modules.common.domain.models.{ApiContext, ApiVersionNbr, ClientId} +import uk.gov.hmrc.apiplatform.modules.common.utils.FixedClock + +import uk.gov.hmrc.apisubscriptionfields.model.BoxId +import uk.gov.hmrc.apisubscriptionfields.model.Types._ +import uk.gov.hmrc.apisubscriptionfields.service.PushPullNotificationService + +trait PushPullNotificationServiceMockModule extends MockitoSugar with ArgumentMatchersSugar with FixedClock { + + protected trait BasePushPullNotificationServiceMock { + def aMock: PushPullNotificationService + + def verifyZeroInteractions(): Unit = MockitoSugar.verifyZeroInteractions(aMock) + + object EnsureBoxIsCreated { + + def succeeds(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersionNbr, fieldName: FieldName, boxId: BoxId) = + when(aMock.ensureBoxIsCreated(eqTo(clientId), eqTo(apiContext), eqTo(apiVersion), eqTo(fieldName))(*)).thenReturn(successful(boxId)) + + def fails(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersionNbr, fieldName: FieldName) = + when(aMock.ensureBoxIsCreated(eqTo(clientId), eqTo(apiContext), eqTo(apiVersion), eqTo(fieldName))(*)).thenReturn(failed(new Exception("bang!"))) + } + + object UpdateCallbackUrl { + + def succeeds(clientId: ClientId, boxId: BoxId, fieldValue: FieldValue) = { + when(aMock.updateCallbackUrl(eqTo(clientId), eqTo(boxId), eqTo(fieldValue))(*)).thenReturn(successful(Right(()))) + } + + def fails(clientId: ClientId, boxId: BoxId, fieldValue: FieldValue, error: String) = + when(aMock.updateCallbackUrl(eqTo(clientId), eqTo(boxId), eqTo(fieldValue))(*)).thenReturn(successful(Left(error))) + } + } + + object PushPullNotificationServiceMock extends BasePushPullNotificationServiceMock { + val aMock = mock[PushPullNotificationService](withSettings.strictness(Strictness.Warn)) + } +} diff --git a/test/uk/gov/hmrc/apisubscriptionfields/mocks/SubscriptionFieldsRepositoryMockModule.scala b/test/uk/gov/hmrc/apisubscriptionfields/mocks/SubscriptionFieldsRepositoryMockModule.scala new file mode 100644 index 0000000..83e4195 --- /dev/null +++ b/test/uk/gov/hmrc/apisubscriptionfields/mocks/SubscriptionFieldsRepositoryMockModule.scala @@ -0,0 +1,98 @@ +/* + * 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.apisubscriptionfields.mocks + +import scala.concurrent.Future.{failed, successful} + +import org.mockito.{ArgumentMatchersSugar, MockitoSugar} + +import uk.gov.hmrc.apiplatform.modules.common.domain.models.{ApiContext, ApiVersionNbr, ClientId} +import uk.gov.hmrc.apiplatform.modules.common.utils.FixedClock + +import uk.gov.hmrc.apisubscriptionfields.model.Types._ +import uk.gov.hmrc.apisubscriptionfields.model.{SubscriptionFields, SubscriptionFieldsId} +import uk.gov.hmrc.apisubscriptionfields.repository.SubscriptionFieldsRepository + +trait SubscriptionFieldsRepositoryMockModule extends MockitoSugar with ArgumentMatchersSugar with FixedClock { + + protected trait BaseSubscriptionFieldsRepositoryMock { + def aMock: SubscriptionFieldsRepository + + def verifyZeroInteractions(): Unit = MockitoSugar.verifyZeroInteractions(aMock) + + object FetchAll { + + def returns(list: List[SubscriptionFields]) = + when(aMock.fetchAll).thenReturn(successful(list)) + } + + object FetchByClientId { + + def returns(clientId: ClientId, list: List[SubscriptionFields]) = + when(aMock.fetchByClientId(eqTo(clientId))).thenReturn(successful(list)) + } + + object Fetch { + + def returns(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersionNbr, fields: SubscriptionFields) = + when(aMock.fetch(eqTo(clientId), eqTo(apiContext), eqTo(apiVersion))).thenReturn(successful(Some(fields))) + + def returnsNone(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersionNbr) = + when(aMock.fetch(eqTo(clientId), eqTo(apiContext), eqTo(apiVersion))).thenReturn(successful(None)) + } + + object FetchByFieldsId { + + def returns(id: SubscriptionFieldsId, fields: SubscriptionFields) = + when(aMock.fetchByFieldsId(eqTo(id))).thenReturn(successful(Some(fields))) + + def returnsNone(id: SubscriptionFieldsId) = + when(aMock.fetchByFieldsId(eqTo(id))).thenReturn(successful(None)) + } + + object SaveAtomic { + + def returns(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersionNbr, fields: SubscriptionFields, isInsert: IsInsert) = + when(aMock.saveAtomic(eqTo(clientId), eqTo(apiContext), eqTo(apiVersion), *)).thenReturn(successful((fields, isInsert))) + + def fails(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersionNbr, failure: Exception) = + when(aMock.saveAtomic(eqTo(clientId), eqTo(apiContext), eqTo(apiVersion), *)).thenReturn(failed(failure)) + + def verifyCalled() = + verify(aMock, times(1)).saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *) + } + + object Delete { + + def existsFor(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersionNbr) = + when(aMock.delete(eqTo(clientId), eqTo(apiContext), eqTo(apiVersion))).thenReturn(successful(true)) + + def existsFor(clientId: ClientId) = + when(aMock.delete(eqTo(clientId))).thenReturn(successful(true)) + + def notExistingFor(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersionNbr) = + when(aMock.delete(eqTo(clientId), eqTo(apiContext), eqTo(apiVersion))).thenReturn(successful(false)) + + def notExistingFor(clientId: ClientId) = + when(aMock.delete(eqTo(clientId))).thenReturn(successful(false)) + } + } + + object SubscriptionFieldsRepositoryMock extends BaseSubscriptionFieldsRepositoryMock { + val aMock = mock[SubscriptionFieldsRepository] + } +} diff --git a/test/uk/gov/hmrc/apisubscriptionfields/service/PushPullNotificationServiceSpec.scala b/test/uk/gov/hmrc/apisubscriptionfields/service/PushPullNotificationServiceSpec.scala index d60bc2c..509bd2d 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/service/PushPullNotificationServiceSpec.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/service/PushPullNotificationServiceSpec.scala @@ -17,14 +17,12 @@ package uk.gov.hmrc.apisubscriptionfields.service import java.{util => ju} -import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future.{failed, successful} import uk.gov.hmrc.apiplatform.modules.common.domain.models._ import uk.gov.hmrc.http.HeaderCarrier import uk.gov.hmrc.apisubscriptionfields.connector.PushPullNotificationServiceConnector -import uk.gov.hmrc.apisubscriptionfields.model.FieldDefinitionType._ import uk.gov.hmrc.apisubscriptionfields.model._ import uk.gov.hmrc.apisubscriptionfields.{AsyncHmrcSpec, FieldDefinitionTestData, SubscriptionFieldsTestData} @@ -43,60 +41,52 @@ class PushPullNotificationServiceSpec extends AsyncHmrcSpec with SubscriptionFie val service = new PushPullNotificationService(mockPPNSConnector) } - "subscribing to PPNS" should { - val ppnsFieldName = fieldN(1) - val callbackUrl = "123" - val oCallbackUrl = Some(callbackUrl) - val ppnsFieldDefinition = FieldDefinition(ppnsFieldName, "description-1", "hint-1", PPNS_FIELD, "short-description-1") - val expectedTopicName = s"${apiContext.value}##${apiVersionNbr.value}##$ppnsFieldName" + "creating PPNS box when needed" should { + val ppnsFieldName = fieldN(1) + val expectedTopicName = s"${apiContext.value}##${apiVersionNbr.value}##$ppnsFieldName" - "succeed and return PPNSCallBackUrlSuccessResponse when update of callback URL is successful" in new Setup { + "succeed when box is created" in new Setup { when(mockPPNSConnector.ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*)).thenReturn(successful(boxId)) - when(mockPPNSConnector.updateCallBackUrl(clientId, boxId, callbackUrl)(hc)).thenReturn(successful(PPNSCallBackUrlSuccessResponse)) - val result: PPNSCallBackUrlValidationResponse = await(service.subscribeToPPNS(clientId, apiContext, apiVersionNbr, oCallbackUrl, ppnsFieldDefinition)) + val result = await(service.ensureBoxIsCreated(clientId, apiContext, apiVersionNbr, ppnsFieldName)) - result shouldBe PPNSCallBackUrlSuccessResponse - verify(mockPPNSConnector).ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*) - verify(mockPPNSConnector).updateCallBackUrl(eqTo(clientId), eqTo(boxId), eqTo(callbackUrl))(*) + result shouldBe boxId } - "succeed and return PPNSCallBackUrlSuccessResponse but not call updatecallBackUrl when field is empty" in new Setup { - when(mockPPNSConnector.ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*)).thenReturn(successful(boxId)) - - val result: PPNSCallBackUrlValidationResponse = await(service.subscribeToPPNS(clientId, apiContext, apiVersionNbr, None, ppnsFieldDefinition)) + "fail when box creation fails" in new Setup { + when(mockPPNSConnector.ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*)).thenReturn(failed(new RuntimeException)) - result shouldBe PPNSCallBackUrlSuccessResponse - verify(mockPPNSConnector).ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*) - verify(mockPPNSConnector, times(0)).updateCallBackUrl(eqTo(clientId), eqTo(boxId), eqTo(callbackUrl))(*) + intercept[RuntimeException] { + await(service.ensureBoxIsCreated(clientId, apiContext, apiVersionNbr, ppnsFieldName)) + } } + } - "return PPNSCallBackUrlFailedResponse when update of callback URL fails" in new Setup { - val errorMessage = "Error Message" - when(mockPPNSConnector.ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*)).thenReturn(successful(boxId)) - when(mockPPNSConnector.updateCallBackUrl(clientId, boxId, callbackUrl)(hc)).thenReturn(successful(PPNSCallBackUrlFailedResponse(errorMessage))) + "updating PPNS callback URL" should { + val ppnsFieldValue = "localhost:9001/pingme" - val result: PPNSCallBackUrlValidationResponse = await(service.subscribeToPPNS(clientId, apiContext, apiVersionNbr, oCallbackUrl, ppnsFieldDefinition)) + "succeed when update of callback URL is successful" in new Setup { + when(mockPPNSConnector.updateCallBackUrl(clientId, boxId, ppnsFieldValue)(hc)).thenReturn(successful(Right(()))) - result shouldBe PPNSCallBackUrlFailedResponse(errorMessage) - verify(mockPPNSConnector).ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*) - verify(mockPPNSConnector).updateCallBackUrl(eqTo(clientId), eqTo(boxId), eqTo(callbackUrl))(*) + val result = await(service.updateCallbackUrl(clientId, boxId, ppnsFieldValue)) + + result shouldBe Right(()) } - "fail when box creation fails" in new Setup { - when(mockPPNSConnector.ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*)).thenReturn(failed(new RuntimeException)) + "fail when update of callback URL fails with an error message" in new Setup { + val errorMessage = "Error Message" + when(mockPPNSConnector.updateCallBackUrl(clientId, boxId, ppnsFieldValue)(hc)).thenReturn(successful(Left(errorMessage))) - intercept[RuntimeException] { - await(service.subscribeToPPNS(clientId, apiContext, apiVersionNbr, oCallbackUrl, ppnsFieldDefinition)) - } + val result = await(service.updateCallbackUrl(clientId, boxId, ppnsFieldValue)) + + result shouldBe Left(errorMessage) } - "fail when update callback url fails" in new Setup { - when(mockPPNSConnector.ensureBoxIsCreated(eqTo(expectedTopicName), eqTo(clientId))(*)).thenReturn(successful(boxId)) - when(mockPPNSConnector.updateCallBackUrl(clientId, boxId, callbackUrl)(hc)).thenReturn(failed(new RuntimeException)) + "fail when update of callback URL fails with an exception" in new Setup { + when(mockPPNSConnector.updateCallBackUrl(clientId, boxId, ppnsFieldValue)(hc)).thenReturn(failed(new RuntimeException)) intercept[RuntimeException] { - await(service.subscribeToPPNS(clientId, apiContext, apiVersionNbr, oCallbackUrl, ppnsFieldDefinition)) + await(service.updateCallbackUrl(clientId, boxId, ppnsFieldValue)) } } } diff --git a/test/uk/gov/hmrc/apisubscriptionfields/service/SubscriptionFieldsServiceSpec.scala b/test/uk/gov/hmrc/apisubscriptionfields/service/SubscriptionFieldsServiceSpec.scala index 3c5f074..7258752 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/service/SubscriptionFieldsServiceSpec.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/service/SubscriptionFieldsServiceSpec.scala @@ -17,35 +17,62 @@ package uk.gov.hmrc.apisubscriptionfields.service import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future.{failed, successful} import cats.data.NonEmptyList +import org.mockito.scalatest.IdiomaticMockito +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec -import uk.gov.hmrc.apiplatform.modules.common.domain.models._ +import play.api.test.{DefaultAwaitTimeout, FutureAwaits} import uk.gov.hmrc.http.HeaderCarrier -import uk.gov.hmrc.apisubscriptionfields.model.Types.FieldErrorMap +import uk.gov.hmrc.apisubscriptionfields.mocks.{SubscriptionFieldsRepositoryMockModule, _} +import uk.gov.hmrc.apisubscriptionfields.model.Types._ import uk.gov.hmrc.apisubscriptionfields.model._ -import uk.gov.hmrc.apisubscriptionfields.repository._ -import uk.gov.hmrc.apisubscriptionfields.{AsyncHmrcSpec, FieldDefinitionTestData, SubscriptionFieldsTestData} +import uk.gov.hmrc.apisubscriptionfields.{FieldDefinitionTestData, SubscriptionFieldsTestData} -class SubscriptionFieldsServiceSpec extends AsyncHmrcSpec with SubscriptionFieldsTestData with FieldDefinitionTestData { - - trait Setup { - val mockSubscriptionFieldsRepository: SubscriptionFieldsMongoRepository = mock[SubscriptionFieldsMongoRepository] - val mockApiFieldDefinitionsService: ApiFieldDefinitionsService = mock[ApiFieldDefinitionsService] - val mockPushPullNotificationService: PushPullNotificationService = mock[PushPullNotificationService](org.mockito.Mockito.withSettings().verboseLogging()) +class SubscriptionFieldsServiceSpec extends AnyWordSpec with DefaultAwaitTimeout with FutureAwaits with Matchers with SubscriptionFieldsTestData with FieldDefinitionTestData + with IdiomaticMockito { + trait Setup extends ApiFieldDefinitionsServiceMockModule with SubscriptionFieldsRepositoryMockModule with PushPullNotificationServiceMockModule { implicit val hc: HeaderCarrier = HeaderCarrier() val service = - new SubscriptionFieldsService(mockSubscriptionFieldsRepository, mockApiFieldDefinitionsService, mockPushPullNotificationService) + new SubscriptionFieldsService(SubscriptionFieldsRepositoryMock.aMock, ApiFieldDefinitionsServiceMock.aMock, PushPullNotificationServiceMock.aMock) + + val validSubsFields: SubscriptionFields = subsFieldsFor(SubscriptionFieldsMatchRegexValidation) + val otherValidSubsFields = subsFieldsFor(SubscriptionFieldsMatchRegexValidation + (AlphanumericFieldName -> "CBA321")) + def validSubsFieldsWithPpnsValue(fieldValue: FieldValue) = subsFieldsFor(SubscriptionFieldsMatchRegexValidation + (PPNSFieldFieldName -> fieldValue)) + val validPpnsSubsFields: SubscriptionFields = validSubsFieldsWithPpnsValue(PPNSFieldFieldValue) + + val subsFieldsFailingValidation: SubscriptionFields = subsFieldsFor(Map(AlphanumericFieldName -> "ABC 123", PasswordFieldName -> "Qw12@er")) + val noSubsFields = validSubsFields.copy(fields = Map.empty) + + def thereAreNoFieldDefinitions() = ApiFieldDefinitionsServiceMock.Get.returnsNothing(FakeContext, FakeVersion) + def thereAreFieldDefinitions() = ApiFieldDefinitionsServiceMock.Get.returns(FakeContext, FakeVersion, FakeApiFieldDefinitionsResponseWithRegex) + def thereArePpnsFieldDefinitions() = ApiFieldDefinitionsServiceMock.Get.returns(FakeContext, FakeVersion, FakeApiFieldDefinitionsResponsePPNSWithRegex) + + def thereAreNoExistingFieldValues() = SubscriptionFieldsRepositoryMock.Fetch.returnsNone(FakeClientId, FakeContext, FakeVersion) + def thereAreExistingFieldValues(fields: SubscriptionFields) = SubscriptionFieldsRepositoryMock.Fetch.returns(FakeClientId, FakeContext, FakeVersion, fields) + def thereAreExistingFieldsWithoutValues() = thereAreExistingFieldValues(noSubsFields) + def thereAreExistingPpnsFieldValues() = thereAreExistingFieldValues(validPpnsSubsFields) + + def callToEnsurePpnsBoxIsPresent() = PushPullNotificationServiceMock.EnsureBoxIsCreated.succeeds(FakeClientId, FakeContext, FakeVersion, PPNSFieldFieldName, FakeBoxId) + def callToEnsurePpnsBoxIsPresentFails() = PushPullNotificationServiceMock.EnsureBoxIsCreated.fails(FakeClientId, FakeContext, FakeVersion, PPNSFieldFieldName) + + def ppnsFieldGetsUpdated(fieldValue: FieldValue = PPNSFieldFieldValue) = + PushPullNotificationServiceMock.UpdateCallbackUrl.succeeds(FakeClientId, FakeBoxId, fieldValue) + def ppnsFieldUpdateFails(error: String) = + PushPullNotificationServiceMock.UpdateCallbackUrl.fails(FakeClientId, FakeBoxId, PPNSFieldFieldValue, error) + + def fieldsAreCreatedInDB(whichFields: SubscriptionFields) = SubscriptionFieldsRepositoryMock.SaveAtomic.returns(FakeClientId, FakeContext, FakeVersion, whichFields, true) + def fieldsAreUpdatedInDB(whichFields: SubscriptionFields) = SubscriptionFieldsRepositoryMock.SaveAtomic.returns(FakeClientId, FakeContext, FakeVersion, whichFields, false) } "getAll" should { "return an empty list when no entry exists in the database collection" in new Setup { - when(mockSubscriptionFieldsRepository.fetchAll).thenReturn(successful(List.empty)) + SubscriptionFieldsRepositoryMock.FetchAll.returns(List.empty) await(service.getAll) shouldBe BulkSubscriptionFieldsResponse(subscriptions = List()) } @@ -54,7 +81,7 @@ class SubscriptionFieldsServiceSpec extends AsyncHmrcSpec with SubscriptionField val sf1: SubscriptionFields = createSubscriptionFieldsWithApiContext(FakeClientId) val sf2: SubscriptionFields = createSubscriptionFieldsWithApiContext(FakeClientId2) - when(mockSubscriptionFieldsRepository.fetchAll).thenReturn(successful(List(sf1, sf2))) + SubscriptionFieldsRepositoryMock.FetchAll.returns(List(sf1, sf2)) val expectedResponse: BulkSubscriptionFieldsResponse = BulkSubscriptionFieldsResponse(subscriptions = List( @@ -69,7 +96,7 @@ class SubscriptionFieldsServiceSpec extends AsyncHmrcSpec with SubscriptionField "get by clientId" should { "return None when the expected record does not exist in the database collection" in new Setup { - when(mockSubscriptionFieldsRepository.fetchByClientId(FakeClientId)).thenReturn(successful(List())) + SubscriptionFieldsRepositoryMock.FetchByClientId.returns(FakeClientId, List()) await(service.getByClientId(FakeClientId)) shouldBe None } @@ -77,7 +104,7 @@ class SubscriptionFieldsServiceSpec extends AsyncHmrcSpec with SubscriptionField "return the expected response when the entry exists in the database collection" in new Setup { val sf1: SubscriptionFields = createSubscriptionFieldsWithApiContext() val sf2: SubscriptionFields = createSubscriptionFieldsWithApiContext(rawContext = fakeRawContext2) - when(mockSubscriptionFieldsRepository.fetchByClientId(FakeClientId)).thenReturn(successful(List(sf1, sf2))) + SubscriptionFieldsRepositoryMock.FetchByClientId.returns(FakeClientId, List(sf1, sf2)) val result: Option[BulkSubscriptionFieldsResponse] = await(service.getByClientId(FakeClientId)) @@ -94,13 +121,12 @@ class SubscriptionFieldsServiceSpec extends AsyncHmrcSpec with SubscriptionField "get" should { "return None when no entry exists in the repo" in new Setup { - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(None)) - + thereAreNoExistingFieldValues() await(service.get(FakeClientId, FakeContext, FakeVersion)) shouldBe None } "return the expected response when the entry exists in the database collection" in new Setup { - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiSubscription))) + SubscriptionFieldsRepositoryMock.Fetch.returns(FakeClientId, FakeContext, FakeVersion, FakeApiSubscription) val result: Option[SubscriptionFields] = await(service.get(FakeClientId, FakeContext, FakeVersion)) @@ -110,143 +136,165 @@ class SubscriptionFieldsServiceSpec extends AsyncHmrcSpec with SubscriptionField "get by fieldsId" should { "return None when no entry exists in the repo" in new Setup { - when(mockSubscriptionFieldsRepository.fetchByFieldsId(SubscriptionFieldsId(FakeRawFieldsId))).thenReturn(successful(None)) + SubscriptionFieldsRepositoryMock.FetchByFieldsId.returnsNone(SubscriptionFieldsId(FakeRawFieldsId)) await(service.getBySubscriptionFieldId(FakeFieldsId)) shouldBe None } "return the expected response when the entry exists in the database collection" in new Setup { - when(mockSubscriptionFieldsRepository.fetchByFieldsId(SubscriptionFieldsId(FakeRawFieldsId))).thenReturn(successful(Some(FakeApiSubscription))) + SubscriptionFieldsRepositoryMock.FetchByFieldsId.returns(SubscriptionFieldsId(FakeRawFieldsId), FakeApiSubscription) await(service.getBySubscriptionFieldId(FakeFieldsId)) shouldBe Some(FakeSubscriptionFieldsResponse) } } "upsert" should { - val fieldsNonMatch: Types.Fields = SubscriptionFieldsNonMatchRegexValidation - val fields: Types.Fields = SubscriptionFieldsMatchRegexValidation - val subscriptionFieldsNonMatch: SubscriptionFields = subsFieldsFor(fieldsNonMatch) - val subscriptionFieldsMatching: SubscriptionFields = subsFieldsFor(fields) - val ppnsSuccessResponse = successful(PPNSCallBackUrlSuccessResponse) - val ppnsFailureResponse = successful(PPNSCallBackUrlFailedResponse("An Error Occurred")) - - "return false when updating an existing api subscription fields (no PPNS)" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponseWithRegex))) - when(mockSubscriptionFieldsRepository.saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *)).thenReturn(successful((subscriptionFieldsNonMatch, false))) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsMatching)))) + "succeed in creating a new api subscription fields" in new Setup { + thereAreFieldDefinitions() + thereAreExistingFieldsWithoutValues() + fieldsAreCreatedInDB(validSubsFields) - val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsMatchRegexValidation)) + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, validSubsFields.fields)) - result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, fields), isInsert = false) - verifyZeroInteractions(mockPushPullNotificationService) + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, validSubsFields.fields), isInsert = true) } - "return false when updating an existing api subscription fields where all fields match (no PPNS)" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponseWithRegex))) - when(mockSubscriptionFieldsRepository.saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *)).thenReturn(successful((subscriptionFieldsMatching, false))) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsMatching)))) + "succeed without updating because all fields match (no PPNSField definition)" in new Setup { + thereAreFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) - val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsMatchRegexValidation)) + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, validSubsFields.fields)) - result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, fields), isInsert = false) - verifyZeroInteractions(mockPushPullNotificationService) + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, validSubsFields.fields), isInsert = false) } - "return false when updating an existing api subscription fields (has PPNS)" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponsePPNSWithRegex))) - when(mockPushPullNotificationService.subscribeToPPNS(eqTo(FakeClientId), eqTo(FakeContext), eqTo(FakeVersion), any, any[FieldDefinition])(any)) - .thenReturn(ppnsSuccessResponse) - when(mockSubscriptionFieldsRepository.saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *)).thenReturn(successful((subscriptionFieldsNonMatch, false))) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsNonMatch)))) + "not fail when identical subs fields presented but no definitions exist (anymore)" in new Setup { + thereAreNoFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) - val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsMatchRegexValidationPPNS)) + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, validSubsFields.fields)) - result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, fieldsNonMatch), isInsert = false) - - verify(mockPushPullNotificationService).subscribeToPPNS( - eqTo(FakeClientId), - eqTo(FakeContext), - eqTo(FakeVersion), - eqTo(Some("https://www.mycallbackurl.com")), - any[FieldDefinition] - )(any) - verify(mockSubscriptionFieldsRepository).saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *) + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, validSubsFields.fields), isInsert = false) } - "return false when updating an existing api subscription field with empty string callback URL for PPNS" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponsePPNSWithRegex))) - when(mockPushPullNotificationService.subscribeToPPNS(eqTo(FakeClientId), eqTo(FakeContext), eqTo(FakeVersion), any, any[FieldDefinition])(any)) - .thenReturn(ppnsSuccessResponse) - when(mockSubscriptionFieldsRepository.saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *)).thenReturn(successful((subscriptionFieldsNonMatch, false))) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsNonMatch)))) + "not fail when changed subs fields presented but no definitions exist (anymore)" in new Setup { + thereAreNoFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) + + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, otherValidSubsFields.fields)) - val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsEmptyValueRegexValidationPPNS)) + result shouldBe NotFoundSubsFieldsUpsertResponse + } - result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, fieldsNonMatch), isInsert = false) + "succeed updating an existing api subscription field because fields don't all match (no PPNSField definition)" in new Setup { + thereAreFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) + fieldsAreUpdatedInDB(otherValidSubsFields) - verify(mockPushPullNotificationService).subscribeToPPNS(eqTo(FakeClientId), eqTo(FakeContext), eqTo(FakeVersion), eqTo(Some("")), any[FieldDefinition])(any) - verify(mockSubscriptionFieldsRepository).saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *) + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, otherValidSubsFields.fields)) + + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, otherValidSubsFields.fields), isInsert = false) } - "return PPNSCallBackUrlSuccessResponse when updating an existing api subscription field for PPNS is not included" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponsePPNSWithRegex))) - when(mockPushPullNotificationService.subscribeToPPNS(eqTo(FakeClientId), eqTo(FakeContext), eqTo(FakeVersion), any, any[FieldDefinition])(any)) - .thenReturn(ppnsSuccessResponse) - when(mockSubscriptionFieldsRepository.saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *)).thenReturn(successful((subscriptionFieldsNonMatch, false))) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsNonMatch)))) + "fail validation when upserting with an invalid field value (no PPNSField definition)" in new Setup { + thereAreFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) + + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, subsFieldsFailingValidation.fields)) - val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsMatchRegexValidation)) + result shouldBe FailedValidationSubsFieldsUpsertResponse(Map( + AlphanumericFieldName -> FakeFieldDefinitionAlphnumericField.validation.get.errorMessage, + PasswordFieldName -> FakeFieldDefinitionPassword.validation.get.errorMessage + )) + } - result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, fieldsNonMatch), isInsert = false) + "succeed creating a new api subscription fields with PPNS field value" in new Setup { + thereArePpnsFieldDefinitions() + thereAreExistingFieldsWithoutValues() + callToEnsurePpnsBoxIsPresent() + ppnsFieldGetsUpdated() + fieldsAreCreatedInDB(validPpnsSubsFields) - verify(mockPushPullNotificationService).subscribeToPPNS(eqTo(FakeClientId), eqTo(FakeContext), eqTo(FakeVersion), any, any[FieldDefinition])(any) - verify(mockSubscriptionFieldsRepository).saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *) + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, validPpnsSubsFields.fields)) + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, validPpnsSubsFields.fields), isInsert = true) } - "return FailedValidationSubsFieldsUpsertResponse when updating an existing api subscription fields and PPNS service returns failure" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponsePPNSWithRegex))) - when(mockPushPullNotificationService.subscribeToPPNS(eqTo(FakeClientId), eqTo(FakeContext), eqTo(FakeVersion), any, any[FieldDefinition])(any)) - .thenReturn(ppnsFailureResponse) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsNonMatch)))) + "succeed without updating PPNS field because all fields match but ensure PPNS box creation occurs - APSR-1788" in new Setup { + thereArePpnsFieldDefinitions() + thereAreExistingPpnsFieldValues() + callToEnsurePpnsBoxIsPresent() + + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, validPpnsSubsFields.fields)) + + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, validPpnsSubsFields.fields), isInsert = false) + } - val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsMatchRegexValidationPPNS)) + "succeed when PPNS field definition is added to an API with an already subscribed app, update PPNS field to empty string and ensure PPNS box creation occurs - APSR-1788" in new Setup { + thereArePpnsFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) + callToEnsurePpnsBoxIsPresent() + ppnsFieldGetsUpdated("") + val fieldsWithPpnsEmptyValue = validSubsFieldsWithPpnsValue("") + fieldsAreUpdatedInDB(fieldsWithPpnsEmptyValue) - result shouldBe FailedValidationSubsFieldsUpsertResponse(Map(PPNSFieldFieldName -> "An Error Occurred")) + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, fieldsWithPpnsEmptyValue.fields)) - verify(mockPushPullNotificationService).subscribeToPPNS(eqTo(FakeClientId), eqTo(FakeContext), eqTo(FakeVersion), any, any[FieldDefinition])(any) + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, fieldsWithPpnsEmptyValue.fields), isInsert = false) } - "return FailedValidationSubsFieldsUpsertResponse when updating an existing api subscription fields and PPNS field fails validation" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponsePPNSWithRegex))) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsNonMatch)))) + "handle having no PPNS field value in the new fields but saves other fields due to changed values" in new Setup { + thereArePpnsFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) + fieldsAreUpdatedInDB(otherValidSubsFields) - val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsDoNotMatchRegexValidationPPNS)) + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, otherValidSubsFields.fields)) - result shouldBe FailedValidationSubsFieldsUpsertResponse(Map(PPNSFieldFieldName -> "CallBackUrl Validation")) + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, otherValidSubsFields.fields), isInsert = false) + } + + "handle PPNS subscription returning validation errors" in new Setup { + thereArePpnsFieldDefinitions() + thereAreNoExistingFieldValues() + callToEnsurePpnsBoxIsPresent() + ppnsFieldUpdateFails("Bobbins") + + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, validPpnsSubsFields.fields)) - verifyZeroInteractions(mockPushPullNotificationService) + result shouldBe FailedValidationSubsFieldsUpsertResponse(Map(PPNSFieldFieldName -> "Bobbins")) } - "return true when creating a new api subscription fields" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponseWithRegex))) + "succeed updating an existing api subscription field because fields dont all match and PPNS subscribe is required" in new Setup { + thereArePpnsFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) + callToEnsurePpnsBoxIsPresent() + ppnsFieldGetsUpdated() + fieldsAreUpdatedInDB(validPpnsSubsFields) + + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, validPpnsSubsFields.fields)) + + result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, validPpnsSubsFields.fields), isInsert = false) + } - when(mockSubscriptionFieldsRepository.saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *)).thenReturn(successful((subscriptionFieldsNonMatch, true))) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsNonMatch)))) + "fail validation when upserting with a bad PPNS field value and don't subscribe PPNS" in new Setup { + thereArePpnsFieldDefinitions() + SubscriptionFieldsRepositoryMock.Fetch.returns(FakeClientId, FakeContext, FakeVersion, subsFieldsFailingValidation) - val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsMatchRegexValidation)) + final val BadPpnsValue = "xxx" + val baseFields = SubscriptionFieldsMatchRegexValidationPPNS + val fieldsWithInvalidPpnsValue = baseFields + (PPNSFieldFieldName -> BadPpnsValue) + val result: SubsFieldsUpsertResponse = await(service.upsert(FakeClientId, FakeContext, FakeVersion, fieldsWithInvalidPpnsValue)) - result shouldBe SuccessfulSubsFieldsUpsertResponse(SubscriptionFields(FakeClientId, FakeContext, FakeVersion, FakeFieldsId, fieldsNonMatch), isInsert = true) - verifyZeroInteractions(mockPushPullNotificationService) + result shouldBe FailedValidationSubsFieldsUpsertResponse(Map(PPNSFieldFieldName -> FakeFieldDefinitionPPNSFields.validation.get.errorMessage)) } "propagate the error" in new Setup { - when(mockApiFieldDefinitionsService.get(FakeContext, FakeVersion)).thenReturn(successful(Some(FakeApiFieldDefinitionsResponseWithRegex))) - when(mockSubscriptionFieldsRepository.saveAtomic(*[ClientId], *[ApiContext], *[ApiVersionNbr], *)).thenReturn(failed(emulatedFailure)) - when(mockSubscriptionFieldsRepository.fetch(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(Some((subscriptionFieldsNonMatch)))) + thereAreFieldDefinitions() + thereAreExistingFieldValues(validSubsFields) + SubscriptionFieldsRepositoryMock.SaveAtomic.fails(FakeClientId, FakeContext, FakeVersion, emulatedFailure) val caught: EmulatedFailure = intercept[EmulatedFailure] { - await(service.upsert(FakeClientId, FakeContext, FakeVersion, SubscriptionFieldsMatchRegexValidation)) + await(service.upsert(FakeClientId, FakeContext, FakeVersion, otherValidSubsFields.fields)) } caught shouldBe emulatedFailure @@ -255,25 +303,25 @@ class SubscriptionFieldsServiceSpec extends AsyncHmrcSpec with SubscriptionField "delete" should { "return true when the entry exists in the database collection" in new Setup { - when(mockSubscriptionFieldsRepository.delete(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(true)) + SubscriptionFieldsRepositoryMock.Delete.existsFor(FakeClientId, FakeContext, FakeVersion) await(service.delete(FakeClientId, FakeContext, FakeVersion)) shouldBe true } "return false when the entry does not exist in the database collection" in new Setup { - when(mockSubscriptionFieldsRepository.delete(FakeClientId, FakeContext, FakeVersion)).thenReturn(successful(false)) + SubscriptionFieldsRepositoryMock.Delete.notExistingFor(FakeClientId, FakeContext, FakeVersion) await(service.delete(FakeClientId, FakeContext, FakeVersion)) shouldBe false } "return true when the client ID exists in the database collection" in new Setup { - when(mockSubscriptionFieldsRepository.delete(FakeClientId)).thenReturn(successful(true)) + SubscriptionFieldsRepositoryMock.Delete.existsFor(FakeClientId) await(service.delete(FakeClientId)) shouldBe true } "return false when the client ID does not exist in the database collection" in new Setup { - when(mockSubscriptionFieldsRepository.delete(FakeClientId)).thenReturn(successful(false)) + SubscriptionFieldsRepositoryMock.Delete.notExistingFor(FakeClientId) await(service.delete(FakeClientId)) shouldBe false } @@ -295,7 +343,7 @@ class SubscriptionFieldsServiceSpec extends AsyncHmrcSpec with SubscriptionField } } - "validate value against field defintion" should { + "validate value against field definition" should { val fieldDefintionWithoutValidation = FieldDefinition(fieldN(1), "desc1", "hint1", FieldDefinitionType.URL, "short description", None) val fieldDefinitionWithValidation = fieldDefintionWithoutValidation.copy(validation = Some(validationGroup1))