From 2874ccabcde7b697c317dad966d6df20497ef1d3 Mon Sep 17 00:00:00 2001 From: Alistair Macdonald <1460971+nubz@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:13:34 +0000 Subject: [PATCH] APB-8939 - populate existing main agent (#309) * APB-8939 - populate existing main agent --- .../auth/AuthActions.scala | 28 ++- .../AgentFiRelationshipConnector.scala | 22 +- .../InvitationLinkController.scala | 72 ++++--- .../model/IrvRelationship.scala | 26 +++ .../invitationLink/ExistingMainAgent.scala | 28 +++ .../ValidateInvitationResponse.scala | 3 +- .../repository/PartialAuthRepository.scala | 11 + .../services/CheckRelationshipsService.scala | 101 ++++++++- .../services/FindRelationshipsService.scala | 5 +- .../services/InvitationService.scala | 8 + .../InvitationLinkControllerISpec.scala | 193 ++++++++++++++++-- .../RelationshipsBaseControllerISpec.scala | 34 +-- .../stubs/AuthStub.scala | 47 ++++- .../stubs/EnrolmentStoreProxyStubs.scala | 42 +++- .../support/TestData.scala | 23 +++ .../ValidateInvitationResponseSpec.scala | 3 +- .../CheckRelationshipServiceSpec.scala | 83 +++++++- 17 files changed, 638 insertions(+), 91 deletions(-) create mode 100644 app/uk/gov/hmrc/agentclientrelationships/model/IrvRelationship.scala create mode 100644 app/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ExistingMainAgent.scala diff --git a/app/uk/gov/hmrc/agentclientrelationships/auth/AuthActions.scala b/app/uk/gov/hmrc/agentclientrelationships/auth/AuthActions.scala index dac55820..af0475d1 100644 --- a/app/uk/gov/hmrc/agentclientrelationships/auth/AuthActions.scala +++ b/app/uk/gov/hmrc/agentclientrelationships/auth/AuthActions.scala @@ -19,7 +19,7 @@ package uk.gov.hmrc.agentclientrelationships.auth import play.api.Logging import play.api.mvc._ import uk.gov.hmrc.agentclientrelationships.controllers.ErrorResults._ -import uk.gov.hmrc.agentclientrelationships.model._ +import uk.gov.hmrc.agentclientrelationships.model.{EnrolmentKey => LocalEnrolmentKey, _} import uk.gov.hmrc.agentmtdidentifiers.model.{Enrolment => _, _} import uk.gov.hmrc.auth.core.AffinityGroup.{Individual, Organisation} import uk.gov.hmrc.auth.core.AuthProvider.{GovernmentGateway, PrivilegedApplication} @@ -139,21 +139,39 @@ trait AuthActions extends AuthorisedFunctions with Logging { } } - // BTA & PTA Call + // Authorisation request response is a special case where we need to check for multiple services + def withAuthorisedClientForServiceKeys[A, T](serviceKeys: Seq[String])( + body: Seq[LocalEnrolmentKey] => Future[Result] + )(implicit ec: ExecutionContext, hc: HeaderCarrier): Future[Result] = + authorised(AuthProviders(GovernmentGateway) and (Individual or Organisation)) + .retrieve(allEnrolments) { enrolments => + val requiredEnrolments = for { + serviceKey <- serviceKeys + enrolment <- enrolments.getEnrolment(serviceKey) + } yield ( + LocalEnrolmentKey(serviceKey, enrolment.identifiers.map(id => Identifier(id.key, id.value))) + ) + + requiredEnrolments match { + case s if s.isEmpty => Future.successful(NoPermissionToPerformOperation) + case _ => body(requiredEnrolments) + } + } + def withAuthorisedAsClient[A, T]( body: Map[Service, TaxIdentifier] => Future[Result] )(implicit ec: ExecutionContext, hc: HeaderCarrier): Future[Result] = authorised(AuthProviders(GovernmentGateway) and (Individual or Organisation)) .retrieve(allEnrolments) { enrolments => - val identifiers: Map[Service, TaxIdentifier] = (for { + val identifiers = for { supportedService <- supportedServices enrolment <- enrolments.getEnrolment(supportedService.enrolmentKey) clientId <- enrolment.identifiers.headOption - } yield (supportedService, supportedService.supportedClientIdType.createUnderlying(clientId.value))).toMap + } yield (supportedService, supportedService.supportedClientIdType.createUnderlying(clientId.value)) identifiers match { case s if s.isEmpty => Future.successful(NoPermissionToPerformOperation) - case _ => body(identifiers) + case _ => body(identifiers.toMap) } } diff --git a/app/uk/gov/hmrc/agentclientrelationships/connectors/AgentFiRelationshipConnector.scala b/app/uk/gov/hmrc/agentclientrelationships/connectors/AgentFiRelationshipConnector.scala index b22c2a45..c2fe58c4 100644 --- a/app/uk/gov/hmrc/agentclientrelationships/connectors/AgentFiRelationshipConnector.scala +++ b/app/uk/gov/hmrc/agentclientrelationships/connectors/AgentFiRelationshipConnector.scala @@ -19,7 +19,7 @@ package uk.gov.hmrc.agentclientrelationships.connectors import play.api.http.Status.{CREATED, NOT_FOUND, OK} import play.api.libs.json.{Json, Reads} import uk.gov.hmrc.agentclientrelationships.config.AppConfig -import uk.gov.hmrc.agentclientrelationships.model.{ActiveRelationship, InactiveRelationship} +import uk.gov.hmrc.agentclientrelationships.model.{ActiveRelationship, InactiveRelationship, IrvRelationship} import uk.gov.hmrc.agentclientrelationships.util.HttpAPIMonitor import uk.gov.hmrc.agentmtdidentifiers.model.Arn import uk.gov.hmrc.http.HttpReads.Implicits._ @@ -120,4 +120,24 @@ class AgentFiRelationshipConnector @Inject() (appConfig: AppConfig, http: HttpCl } } } + + def findRelationshipForClient(clientId: String)(implicit hc: HeaderCarrier): Future[Option[IrvRelationship]] = + monitor(s"ConsumedAPI-AgentFiRelationship-GET") { + http + .get( + url"${appConfig.agentFiRelationshipBaseUrl}/agent-fi-relationship/relationships/service/PERSONAL-INCOME-RECORD/clientId/$clientId" + ) + .execute[HttpResponse] + .map { response => + response.status match { + case OK => response.json.as[List[IrvRelationship]].headOption + case NOT_FOUND => None + case status => + throw UpstreamErrorResponse( + s"Unexpected status $status received from AFI get active relationship for client", + status + ) + } + } + } } diff --git a/app/uk/gov/hmrc/agentclientrelationships/controllers/InvitationLinkController.scala b/app/uk/gov/hmrc/agentclientrelationships/controllers/InvitationLinkController.scala index b1b93cb5..212a3a83 100644 --- a/app/uk/gov/hmrc/agentclientrelationships/controllers/InvitationLinkController.scala +++ b/app/uk/gov/hmrc/agentclientrelationships/controllers/InvitationLinkController.scala @@ -21,10 +21,10 @@ import play.api.libs.json.Json import play.api.mvc.{Action, AnyContent, ControllerComponents} import uk.gov.hmrc.agentclientrelationships.auth.AuthActions import uk.gov.hmrc.agentclientrelationships.config.AppConfig +import uk.gov.hmrc.agentclientrelationships.model.EnrolmentKey import uk.gov.hmrc.agentclientrelationships.model.invitationLink.InvitationLinkFailureResponse._ import uk.gov.hmrc.agentclientrelationships.model.invitationLink.{ValidateInvitationRequest, ValidateInvitationResponse} -import uk.gov.hmrc.agentclientrelationships.repository.InvitationsRepository -import uk.gov.hmrc.agentclientrelationships.services.InvitationLinkService +import uk.gov.hmrc.agentclientrelationships.services.{CheckRelationshipsService, InvitationLinkService, InvitationService} import uk.gov.hmrc.agentmtdidentifiers.model.Service import uk.gov.hmrc.agentmtdidentifiers.model.Service.{HMRCMTDIT, HMRCMTDITSUPP} import uk.gov.hmrc.auth.core.AuthConnector @@ -36,7 +36,8 @@ import scala.concurrent.{ExecutionContext, Future} @Singleton class InvitationLinkController @Inject() ( agentReferenceService: InvitationLinkService, - invitationsRepository: InvitationsRepository, + invitationService: InvitationService, + checkRelationshipsService: CheckRelationshipsService, val authConnector: AuthConnector, val appConfig: AppConfig, cc: ControllerComponents @@ -72,34 +73,49 @@ class InvitationLinkController @Inject() ( // TODO: this is a duplicate of what's used in the ClientDetailsController - we really want centralised config private val multiAgentServices: Map[String, String] = Map(HMRCMTDIT -> HMRCMTDITSUPP) + private def servicesToSearchInvitationsFor(enrolments: Seq[EnrolmentKey], serviceKeys: Seq[String]): Set[String] = { + val suppServices = + serviceKeys.filter(multiAgentServices.contains).map(service => multiAgentServices(service)) + (enrolments.map(_.service) ++ suppServices).map { + case "HMRC-NI" | "HMRC-PT" if serviceKeys.contains("HMRC-MTD-IT") => "HMRC-MTD-IT" + case "HMRC-NI" | "HMRC-PT" => "PERSONAL-INCOME-RECORD" + case serviceKey => serviceKey + }.toSet + } + def validateInvitationForClient: Action[ValidateInvitationRequest] = Action.async(parse.json[ValidateInvitationRequest]) { implicit request => - withAuthorisedAsClient { enrolments => - val targetServices = request.body.serviceKeys - val targetEnrolments = enrolments.view.filterKeys(key => targetServices.contains(key.enrolmentKey)).toMap + withAuthorisedClientForServiceKeys(request.body.serviceKeys) { enrolments => agentReferenceService.validateInvitationRequest(request.body.uid).flatMap { - case Right(validateLinkModel) => - val mainServices = targetEnrolments.keys.map(_.id).toSeq - val suppServices = - mainServices.filter(multiAgentServices.contains).map(service => multiAgentServices(service)) - val servicesToSearch = mainServices ++ suppServices - val clientIds = targetEnrolments.values.map(_.value).toSeq - invitationsRepository.findAllForAgent(validateLinkModel.arn.value, servicesToSearch, clientIds).map { - case Seq(invitation) => - val response = ValidateInvitationResponse( - invitation.invitationId, - invitation.service, - validateLinkModel.name, - invitation.status, - invitation.lastUpdated - ) - Ok(Json.toJson(response)) - case _ => - Logger(getClass).warn( - s"Invitation was not found for UID: ${request.body.uid}, service keys: ${request.body.serviceKeys}" - ) - NotFound - } + case Right(validateLinkResponse) => + val servicesToSearch = servicesToSearchInvitationsFor(enrolments, request.body.serviceKeys) + val clientIdsToSearch = enrolments.map(e => e.oneTaxIdentifier()).map(_.value) + invitationService + .findAllForAgent(validateLinkResponse.arn.value, servicesToSearch, clientIdsToSearch) + .flatMap { + case Seq(invitation) => + for { + existingRelationship <- + checkRelationshipsService + .findCurrentMainAgent(invitation, enrolments.find(_.service == invitation.service)) + } yield Ok( + Json.toJson( + ValidateInvitationResponse( + invitation.invitationId, + invitation.service, + validateLinkResponse.name, + invitation.status, + invitation.lastUpdated, + existingMainAgent = existingRelationship + ) + ) + ) + case _ => + Logger(getClass).warn( + s"Invitation was not found for UID: ${request.body.uid}, service keys: ${request.body.serviceKeys}" + ) + Future.successful(NotFound) + } case Left(AgentSuspended) => Logger(getClass).warn(s"Agent is suspended for UID: ${request.body.uid}") Future(Forbidden) diff --git a/app/uk/gov/hmrc/agentclientrelationships/model/IrvRelationship.scala b/app/uk/gov/hmrc/agentclientrelationships/model/IrvRelationship.scala new file mode 100644 index 00000000..8f4acfcf --- /dev/null +++ b/app/uk/gov/hmrc/agentclientrelationships/model/IrvRelationship.scala @@ -0,0 +1,26 @@ +/* + * 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.agentclientrelationships.model + +import play.api.libs.json.{Json, OFormat} +import uk.gov.hmrc.agentmtdidentifiers.model.Arn + +case class IrvRelationship(arn: Arn) + +object IrvRelationship { + implicit val format: OFormat[IrvRelationship] = Json.format[IrvRelationship] +} diff --git a/app/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ExistingMainAgent.scala b/app/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ExistingMainAgent.scala new file mode 100644 index 00000000..b6ab20c3 --- /dev/null +++ b/app/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ExistingMainAgent.scala @@ -0,0 +1,28 @@ +/* + * 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.agentclientrelationships.model.invitationLink + +import play.api.libs.json._ + +case class ExistingMainAgent( + agencyName: String, + sameAgent: Boolean +) + +object ExistingMainAgent { + implicit val format: Format[ExistingMainAgent] = Json.format[ExistingMainAgent] +} diff --git a/app/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ValidateInvitationResponse.scala b/app/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ValidateInvitationResponse.scala index 078f1d9e..3511e437 100644 --- a/app/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ValidateInvitationResponse.scala +++ b/app/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ValidateInvitationResponse.scala @@ -26,7 +26,8 @@ case class ValidateInvitationResponse( serviceKey: String, agentName: String, status: InvitationStatus, - lastModifiedDate: Instant + lastModifiedDate: Instant, + existingMainAgent: Option[ExistingMainAgent] ) object ValidateInvitationResponse { diff --git a/app/uk/gov/hmrc/agentclientrelationships/repository/PartialAuthRepository.scala b/app/uk/gov/hmrc/agentclientrelationships/repository/PartialAuthRepository.scala index 1f1cdabd..7a666af3 100644 --- a/app/uk/gov/hmrc/agentclientrelationships/repository/PartialAuthRepository.scala +++ b/app/uk/gov/hmrc/agentclientrelationships/repository/PartialAuthRepository.scala @@ -67,6 +67,17 @@ class PartialAuthRepository @Inject() (mongoComponent: MongoComponent)(implicit ) .headOption() + /* this will only find partially authorised ITSA main agents for a given nino string */ + def findMainAgent(nino: String): Future[Option[PartialAuthRelationship]] = + collection + .find( + and( + equal("service", HMRCMTDIT), + equal("nino", nino) + ) + ) + .headOption() + def deletePartialAuth(serviceId: String, nino: Nino, arn: Arn): Future[Boolean] = collection .deleteOne( diff --git a/app/uk/gov/hmrc/agentclientrelationships/services/CheckRelationshipsService.scala b/app/uk/gov/hmrc/agentclientrelationships/services/CheckRelationshipsService.scala index 7b4cb6c6..ca5922cc 100644 --- a/app/uk/gov/hmrc/agentclientrelationships/services/CheckRelationshipsService.scala +++ b/app/uk/gov/hmrc/agentclientrelationships/services/CheckRelationshipsService.scala @@ -16,14 +16,18 @@ package uk.gov.hmrc.agentclientrelationships.services -import uk.gov.hmrc.play.bootstrap.metrics.Metrics import play.api.Logging import uk.gov.hmrc.agentclientrelationships.connectors._ -import uk.gov.hmrc.agentclientrelationships.model.{EnrolmentKey, UserId} +import uk.gov.hmrc.agentclientrelationships.model.invitationLink.ExistingMainAgent +import uk.gov.hmrc.agentclientrelationships.model.{EnrolmentKey => LocalEnrolmentKey, Invitation, UserId} +import uk.gov.hmrc.agentclientrelationships.repository.PartialAuthRepository import uk.gov.hmrc.agentclientrelationships.support.Monitoring -import uk.gov.hmrc.agentmtdidentifiers.model.{Arn, Enrolment} import uk.gov.hmrc.agentmtdidentifiers.model.EnrolmentKey.enrolmentKey +import uk.gov.hmrc.agentmtdidentifiers.model.Service.{HMRCCBCORG, HMRCMTDIT, HMRCMTDITSUPP, HMRCPIR} +import uk.gov.hmrc.agentmtdidentifiers.model.{Arn, Enrolment} +import uk.gov.hmrc.domain.Nino import uk.gov.hmrc.http.HeaderCarrier +import uk.gov.hmrc.play.bootstrap.metrics.Metrics import javax.inject.{Inject, Singleton} import scala.concurrent.{ExecutionContext, Future} @@ -32,12 +36,16 @@ import scala.concurrent.{ExecutionContext, Future} class CheckRelationshipsService @Inject() ( es: EnrolmentStoreProxyConnector, ap: AgentPermissionsConnector, + agentAssuranceConnector: AgentAssuranceConnector, + ifConnector: IFConnector, groupSearch: UsersGroupsSearchConnector, + partialAuthRepository: PartialAuthRepository, + agentFiRelationshipConnector: AgentFiRelationshipConnector, val metrics: Metrics ) extends Monitoring with Logging { - def checkForRelationship(arn: Arn, userId: Option[UserId], enrolmentKey: EnrolmentKey)(implicit + def checkForRelationship(arn: Arn, userId: Option[UserId], enrolmentKey: LocalEnrolmentKey)(implicit ec: ExecutionContext, hc: HeaderCarrier ): Future[Boolean] = userId match { @@ -45,7 +53,7 @@ class CheckRelationshipsService @Inject() ( case Some(userId) => checkForRelationshipUserLevel(arn, userId, enrolmentKey) } - def checkForRelationshipAgencyLevel(arn: Arn, enrolmentKey: EnrolmentKey)(implicit + def checkForRelationshipAgencyLevel(arn: Arn, enrolmentKey: LocalEnrolmentKey)(implicit ec: ExecutionContext, hc: HeaderCarrier ): Future[(Boolean, String)] = @@ -55,7 +63,7 @@ class CheckRelationshipsService @Inject() ( groupHasAssignedEnrolment = allocatedGroupIds.contains(groupId) } yield (groupHasAssignedEnrolment, groupId) - def checkForRelationshipUserLevel(arn: Arn, userId: UserId, enrolmentKey: EnrolmentKey)(implicit + def checkForRelationshipUserLevel(arn: Arn, userId: UserId, enrolmentKey: LocalEnrolmentKey)(implicit ec: ExecutionContext, hc: HeaderCarrier ): Future[Boolean] = @@ -92,4 +100,85 @@ class CheckRelationshipsService @Inject() ( private def enrolmentKeys(enrolment: Enrolment): Seq[String] = enrolment.identifiers.map(identifier => enrolmentKey(enrolment.service, identifier.value)) + private def getArnForDelegatedEnrolmentKey( + enrolKey: LocalEnrolmentKey + )(implicit hc: HeaderCarrier, ec: ExecutionContext): Future[Option[Arn]] = + for { + maybeGroupId <- es.getDelegatedGroupIdsFor(enrolKey) + maybeArn <- maybeGroupId.headOption match { + case Some(groupId) => es.getAgentReferenceNumberFor(groupId) + case None => Future.successful(None) + } + } yield maybeArn + + private def findMainAgentForNino( + invitation: Invitation + )(implicit hc: HeaderCarrier, ec: ExecutionContext): Future[Option[ExistingMainAgent]] = + partialAuthRepository.findMainAgent(invitation.clientId).flatMap { + case Some(p) => + agentAssuranceConnector + .getAgentRecordWithChecks(Arn(p.arn)) + .map(agent => + Some( + ExistingMainAgent( + agencyName = agent.agencyDetails.agencyName, + sameAgent = p.arn == invitation.arn + ) + ) + ) + case None => + ifConnector.getMtdIdFor(Nino(invitation.clientId)).flatMap { + case Some(mtdItId) => + getArnForDelegatedEnrolmentKey(LocalEnrolmentKey(enrolmentKey(HMRCMTDIT, mtdItId.value))).flatMap { + case Some(a) => returnExistingMainAgentFromArn(a.value, a.value == invitation.arn) + case None => Future.successful(None) + } + case _ => Future.successful(None) + } + } + + private def returnExistingMainAgentFromArn( + arn: String, + sameAgent: Boolean + )(implicit hc: HeaderCarrier, ec: ExecutionContext): Future[Some[ExistingMainAgent]] = + agentAssuranceConnector + .getAgentRecordWithChecks(Arn(arn)) + .map(agent => + Some( + ExistingMainAgent( + agencyName = agent.agencyDetails.agencyName, + sameAgent = sameAgent + ) + ) + ) + + def findCurrentMainAgent( + invitation: Invitation, + enrolment: Option[LocalEnrolmentKey] + )(implicit hc: HeaderCarrier, ec: ExecutionContext): Future[Option[ExistingMainAgent]] = + invitation.service match { + case HMRCMTDIT | HMRCMTDITSUPP if Nino.isValid(invitation.clientId) => findMainAgentForNino(invitation) + case HMRCPIR => + agentFiRelationshipConnector.findRelationshipForClient(invitation.clientId).flatMap { + case Some(r) => returnExistingMainAgentFromArn(r.arn.value, invitation.arn == r.arn.value) + case None => Future.successful(None) + } + case HMRCCBCORG if enrolment.isDefined => + getArnForDelegatedEnrolmentKey(enrolment.get).flatMap { + case Some(a) => returnExistingMainAgentFromArn(a.value, a.value == invitation.arn) + case None => Future.successful(None) + } + case _ => + getArnForDelegatedEnrolmentKey( + LocalEnrolmentKey( + enrolmentKey( + if (invitation.service == HMRCMTDITSUPP) HMRCMTDIT else invitation.service, + invitation.clientId + ) + ) + ).flatMap { + case Some(a) => returnExistingMainAgentFromArn(a.value, a.value == invitation.arn) + case None => Future.successful(None) + } + } } diff --git a/app/uk/gov/hmrc/agentclientrelationships/services/FindRelationshipsService.scala b/app/uk/gov/hmrc/agentclientrelationships/services/FindRelationshipsService.scala index 114c1869..bc21a8c5 100644 --- a/app/uk/gov/hmrc/agentclientrelationships/services/FindRelationshipsService.scala +++ b/app/uk/gov/hmrc/agentclientrelationships/services/FindRelationshipsService.scala @@ -16,9 +16,6 @@ package uk.gov.hmrc.agentclientrelationships.services -import uk.gov.hmrc.play.bootstrap.metrics.Metrics - -import javax.inject.{Inject, Singleton} import play.api.Logging import uk.gov.hmrc.agentclientrelationships.config.AppConfig import uk.gov.hmrc.agentclientrelationships.connectors._ @@ -27,7 +24,9 @@ import uk.gov.hmrc.agentclientrelationships.support.Monitoring import uk.gov.hmrc.agentmtdidentifiers.model._ import uk.gov.hmrc.domain.{Nino, TaxIdentifier} import uk.gov.hmrc.http.HeaderCarrier +import uk.gov.hmrc.play.bootstrap.metrics.Metrics +import javax.inject.{Inject, Singleton} import scala.concurrent.{ExecutionContext, Future} @Singleton diff --git a/app/uk/gov/hmrc/agentclientrelationships/services/InvitationService.scala b/app/uk/gov/hmrc/agentclientrelationships/services/InvitationService.scala index 813c82fb..0d356a39 100644 --- a/app/uk/gov/hmrc/agentclientrelationships/services/InvitationService.scala +++ b/app/uk/gov/hmrc/agentclientrelationships/services/InvitationService.scala @@ -62,6 +62,14 @@ class InvitationService @Inject() ( def findInvitation(arn: String, invitationId: String): Future[Option[Invitation]] = invitationsRepository.findOneById(arn, invitationId) + def findAllForAgent( + arn: String, + services: Set[String], + clientIds: Seq[String], + isSuppliedClientId: Boolean = false + ): Future[Seq[Invitation]] = + invitationsRepository.findAllForAgent(arn, services.toSeq, clientIds, isSuppliedClientId) + private def makeInvitation( arn: Arn, suppliedClientId: ClientId, diff --git a/it/test/uk/gov/hmrc/agentclientrelationships/controllers/InvitationLinkControllerISpec.scala b/it/test/uk/gov/hmrc/agentclientrelationships/controllers/InvitationLinkControllerISpec.scala index 5e934052..6be07a05 100644 --- a/it/test/uk/gov/hmrc/agentclientrelationships/controllers/InvitationLinkControllerISpec.scala +++ b/it/test/uk/gov/hmrc/agentclientrelationships/controllers/InvitationLinkControllerISpec.scala @@ -19,30 +19,39 @@ package uk.gov.hmrc.agentclientrelationships.controllers import play.api.http.Status.NOT_FOUND import play.api.libs.json.Json import play.api.test.FakeRequest -import play.api.test.Helpers.{await, defaultAwaitTimeout, stubControllerComponents} +import play.api.test.Helpers.{await, defaultAwaitTimeout} import uk.gov.hmrc.agentclientrelationships.config.AppConfig -import uk.gov.hmrc.agentclientrelationships.model.invitationLink.{AgentReferenceRecord, ValidateInvitationResponse} -import uk.gov.hmrc.agentclientrelationships.repository.{InvitationsRepository, MongoAgentReferenceRepository} +import uk.gov.hmrc.agentclientrelationships.model.invitationLink.{AgentReferenceRecord, ExistingMainAgent, ValidateInvitationResponse} +import uk.gov.hmrc.agentclientrelationships.repository.{InvitationsRepository, MongoAgentReferenceRepository, PartialAuthRepository} import uk.gov.hmrc.agentclientrelationships.services.InvitationLinkService import uk.gov.hmrc.agentclientrelationships.support.TestData -import uk.gov.hmrc.agentmtdidentifiers.model.Service.{MtdIt, MtdItSupp} +import uk.gov.hmrc.agentmtdidentifiers.model.Service.{Cbc, MtdIt, MtdItSupp} import uk.gov.hmrc.auth.core.AuthConnector -import java.time.LocalDate import java.time.temporal.ChronoUnit +import java.time.{Instant, LocalDate} import scala.concurrent.ExecutionContext class InvitationLinkControllerISpec extends RelationshipsBaseControllerISpec with TestData { val uid = "TestUID" + val existingAgentUid = "ExitingAgentUid" val normalizedAgentName = "TestNormalizedAgentName" + val normalizedExistingAgentName = "ExistingAgent" val agentReferenceRecord: AgentReferenceRecord = AgentReferenceRecord( uid = uid, arn = arn, normalisedAgentNames = Seq(normalizedAgentName, "NormalisedAgentName2") ) + val existingAgentReferenceRecord: AgentReferenceRecord = AgentReferenceRecord( + uid = existingAgentUid, + arn = existingAgentArn, + normalisedAgentNames = Seq(normalizedExistingAgentName) + ) + val invitationsRepo: InvitationsRepository = app.injector.instanceOf[InvitationsRepository] + val partialAuthRepo: PartialAuthRepository = app.injector.instanceOf[PartialAuthRepository] val agentReferenceService: InvitationLinkService = app.injector.instanceOf[InvitationLinkService] val authConnector: AuthConnector = app.injector.instanceOf[AuthConnector] @@ -174,6 +183,7 @@ class InvitationLinkControllerISpec extends RelationshipsBaseControllerISpec wit givenAuditConnector() givenAuthorisedAsClient(fakeRequest, mtdItId, vrn, utr, urn, pptRef, cgtRef) givenAgentRecordFound(arn, agentRecordResponse) + givenDelegatedGroupIdsNotExistFor(mtdItEnrolmentKey) await(agentReferenceRepo.create(agentReferenceRecord)) val pendingInvitation = await(invitationsRepo.create(arn.value, MtdIt, mtdItId, mtdItId, "Erling Haal", LocalDate.now())) @@ -185,20 +195,25 @@ class InvitationLinkControllerISpec extends RelationshipsBaseControllerISpec wit pendingInvitation.service, agentRecord.agencyDetails.agencyName, pendingInvitation.status, - pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS) + pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS), + existingMainAgent = None ) result.status shouldBe 200 result.json shouldBe Json.toJson(expectedResponse) } - "return 200 status and appropriate JSON body when a matching agent and invitation for ITSA supporting agent is found" in { + "return 200 status and appropriate JSON body when a matching agent and invitation plus existing main agent is found" in { givenAuditConnector() givenAuthorisedAsClient(fakeRequest, mtdItId, vrn, utr, urn, pptRef, cgtRef) givenAgentRecordFound(arn, agentRecordResponse) + givenDelegatedGroupIdsExistFor(mtdItEnrolmentKey, Set(testExistingAgentGroup)) + givenGetAgentReferenceNumberFor(testExistingAgentGroup, existingAgentArn.value) + givenAgentRecordFound(existingAgentArn, existingAgentRecordResponse) await(agentReferenceRepo.create(agentReferenceRecord)) + val expectedExistingMainAgent = ExistingMainAgent(agencyName = "ExistingAgent", sameAgent = false) val pendingInvitation = - await(invitationsRepo.create(arn.value, MtdItSupp, mtdItId, nino, "Erling Haal", LocalDate.now())) + await(invitationsRepo.create(arn.value, MtdIt, mtdItId, mtdItId, "Erling Haal", LocalDate.now())) val requestBody = Json.obj("uid" -> uid, "serviceKeys" -> Json.arr("HMRC-MTD-IT")) val result = doAgentPostRequest(fakeRequest.uri, requestBody) @@ -207,47 +222,170 @@ class InvitationLinkControllerISpec extends RelationshipsBaseControllerISpec wit pendingInvitation.service, agentRecord.agencyDetails.agencyName, pendingInvitation.status, - pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS) + pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS), + existingMainAgent = Some(expectedExistingMainAgent) ) result.status shouldBe 200 result.json shouldBe Json.toJson(expectedResponse) } - "return 400 status when invalid JSON is provided" in { + "return 200 status and appropriate JSON body when a matching agent and invitation plus existing same main agent is found" in { givenAuditConnector() + givenAuthorisedAsClient(fakeRequest, mtdItId, vrn, utr, urn, pptRef, cgtRef) + givenAgentRecordFound(existingAgentArn, existingAgentRecordResponse) + givenDelegatedGroupIdsExistFor(mtdItEnrolmentKey, Set(testExistingAgentGroup)) + givenGetAgentReferenceNumberFor(testExistingAgentGroup, existingAgentArn.value) + givenAgentRecordFound(existingAgentArn, existingAgentRecordResponse) + await(agentReferenceRepo.create(existingAgentReferenceRecord)) + val expectedExistingMainAgent = ExistingMainAgent(agencyName = "ExistingAgent", sameAgent = true) + val pendingInvitation = + await( + invitationsRepo.create(existingAgentArn.value, MtdItSupp, mtdItId, nino, "Erling Haal", LocalDate.now()) + ) - val requestBody = Json.obj("foo" -> "bar") + val requestBody = Json.obj("uid" -> existingAgentUid, "serviceKeys" -> Json.arr("HMRC-MTD-IT")) val result = doAgentPostRequest(fakeRequest.uri, requestBody) + val expectedResponse = ValidateInvitationResponse( + pendingInvitation.invitationId, + pendingInvitation.service, + existingAgentRecord.agencyDetails.agencyName, + pendingInvitation.status, + pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS), + existingMainAgent = Some(expectedExistingMainAgent) + ) - result.status shouldBe 400 + result.status shouldBe 200 + result.json shouldBe Json.toJson(expectedResponse) + } + + "return 200 status and correct JSON when a matching agent, invitation and existing main agent with partial auth is found" in { + givenAuditConnector() + givenAuthorisedAsAltItsaClient(fakeRequest, nino) + await(partialAuthRepo.create(created = Instant.now(), existingAgentArn, "HMRC-MTD-IT", nino)) + givenMtdItIdIsUnKnownFor(nino) + givenAgentRecordFound(arn, agentRecordResponse) + givenAgentRecordFound(existingAgentArn, existingAgentRecordResponse) + await(agentReferenceRepo.create(agentReferenceRecord)) + val expectedExistingMainAgent = ExistingMainAgent(agencyName = "ExistingAgent", sameAgent = false) + val pendingInvitation = + await(invitationsRepo.create(arn.value, MtdIt, nino, nino, "Erling Haal", LocalDate.now())) + + val requestBody = Json.obj("uid" -> uid, "serviceKeys" -> Json.arr("HMRC-MTD-IT", "HMRC-NI", "HMRC-PT")) + val result = doAgentPostRequest(fakeRequest.uri, requestBody) + val expectedResponse = ValidateInvitationResponse( + pendingInvitation.invitationId, + pendingInvitation.service, + agentRecord.agencyDetails.agencyName, + pendingInvitation.status, + pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS), + existingMainAgent = Some(expectedExistingMainAgent) + ) + + result.status shouldBe 200 + result.json shouldBe Json.toJson(expectedResponse) } - "return 403 status when the agent is suspended" in { + "return 200 status and correct JSON when a matching agent, invitation and existing same main agent with partial auth is found" in { + givenAuditConnector() + givenAuthorisedAsAltItsaClient(fakeRequest, nino) + await(partialAuthRepo.create(created = Instant.now(), existingAgentArn, "HMRC-MTD-IT", nino)) + givenMtdItIdIsUnKnownFor(nino) + givenAgentRecordFound(existingAgentArn, existingAgentRecordResponse) + await(agentReferenceRepo.create(existingAgentReferenceRecord)) + val expectedExistingMainAgent = ExistingMainAgent(agencyName = "ExistingAgent", sameAgent = true) + val pendingInvitation = + await(invitationsRepo.create(existingAgentArn.value, MtdItSupp, nino, nino, "Erling Haal", LocalDate.now())) + + val requestBody = Json.obj("uid" -> existingAgentUid, "serviceKeys" -> Json.arr("HMRC-MTD-IT", "HMRC-PT")) + val result = doAgentPostRequest(fakeRequest.uri, requestBody) + val expectedResponse = ValidateInvitationResponse( + pendingInvitation.invitationId, + pendingInvitation.service, + existingAgentRecord.agencyDetails.agencyName, + pendingInvitation.status, + pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS), + existingMainAgent = Some(expectedExistingMainAgent) + ) + + result.status shouldBe 200 + result.json shouldBe Json.toJson(expectedResponse) + } + + "return 200 status and appropriate JSON body when a matching agent and invitation for ITSA supporting agent is found" in { givenAuditConnector() givenAuthorisedAsClient(fakeRequest, mtdItId, vrn, utr, urn, pptRef, cgtRef) - givenAgentRecordFound(arn, suspendedAgentRecordResponse) + givenAgentRecordFound(arn, agentRecordResponse) + givenDelegatedGroupIdsNotExistFor(mtdItEnrolmentKey) await(agentReferenceRepo.create(agentReferenceRecord)) - await(invitationsRepo.create(arn.value, MtdIt, mtdItId, mtdItId, "Erling Haal", LocalDate.now())) + val pendingInvitation = + await(invitationsRepo.create(arn.value, MtdItSupp, mtdItId, nino, "Erling Haal", LocalDate.now())) val requestBody = Json.obj("uid" -> uid, "serviceKeys" -> Json.arr("HMRC-MTD-IT")) val result = doAgentPostRequest(fakeRequest.uri, requestBody) + val expectedResponse = ValidateInvitationResponse( + pendingInvitation.invitationId, + pendingInvitation.service, + agentRecord.agencyDetails.agencyName, + pendingInvitation.status, + pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS), + existingMainAgent = None + ) - result.status shouldBe 403 + result.status shouldBe 200 + result.json shouldBe Json.toJson(expectedResponse) } - "return 404 status" when { + "return 200 status and appropriate JSON body when a matching agent, invitation and existing agent for CBC UK is found" in { + givenAuditConnector() + givenAuthorisedAsCbcUkClient(fakeRequest, utr, cbcId) + givenAgentRecordFound(arn, agentRecordResponse) + givenDelegatedGroupIdsExistFor(cbcUkEnrolmentKey, Set(testExistingAgentGroup)) + givenGetAgentReferenceNumberFor(testExistingAgentGroup, existingAgentArn.value) + givenAgentRecordFound(existingAgentArn, existingAgentRecordResponse) + await(agentReferenceRepo.create(agentReferenceRecord)) + val pendingInvitation = + await(invitationsRepo.create(arn.value, Cbc, cbcId, cbcId, "Erling Haal", LocalDate.now())) - "no invitations were found in the invitations collection for the given agent" in { + val requestBody = Json.obj("uid" -> uid, "serviceKeys" -> Json.arr("HMRC-CBC-ORG")) + val result = doAgentPostRequest(fakeRequest.uri, requestBody) + val expectedExistingMainAgent = ExistingMainAgent(agencyName = "ExistingAgent", sameAgent = false) + val expectedResponse = ValidateInvitationResponse( + pendingInvitation.invitationId, + pendingInvitation.service, + agentRecord.agencyDetails.agencyName, + pendingInvitation.status, + pendingInvitation.lastUpdated.truncatedTo(ChronoUnit.MILLIS), + existingMainAgent = Some(expectedExistingMainAgent) + ) + + result.status shouldBe 200 + result.json shouldBe Json.toJson(expectedResponse) + } + + "return 400 status when invalid JSON is provided" in { + givenAuditConnector() + + val requestBody = Json.obj("foo" -> "bar") + val result = doAgentPostRequest(fakeRequest.uri, requestBody) + + result.status shouldBe 400 + } + + "return 403 status" when { + + "the agent is suspended" in { givenAuditConnector() givenAuthorisedAsClient(fakeRequest, mtdItId, vrn, utr, urn, pptRef, cgtRef) - givenAgentRecordFound(arn, agentRecordResponse) + givenAgentRecordFound(arn, suspendedAgentRecordResponse) + givenDelegatedGroupIdsNotExistFor(mtdItEnrolmentKey) await(agentReferenceRepo.create(agentReferenceRecord)) + await(invitationsRepo.create(arn.value, MtdIt, mtdItId, mtdItId, "Erling Haal", LocalDate.now())) val requestBody = Json.obj("uid" -> uid, "serviceKeys" -> Json.arr("HMRC-MTD-IT")) val result = doAgentPostRequest(fakeRequest.uri, requestBody) - result.status shouldBe 404 + result.status shouldBe 403 } "the provided service key does not exist in the client's enrolments" in { @@ -260,6 +398,21 @@ class InvitationLinkControllerISpec extends RelationshipsBaseControllerISpec wit val requestBody = Json.obj("uid" -> uid, "serviceKeys" -> Json.arr("HMRC-MADE-UP")) val result = doAgentPostRequest(fakeRequest.uri, requestBody) + result.status shouldBe 403 + } + } + + "return 404 status" when { + + "no invitations were found in the invitations collection for the given agent" in { + givenAuditConnector() + givenAuthorisedAsClient(fakeRequest, mtdItId, vrn, utr, urn, pptRef, cgtRef) + givenAgentRecordFound(arn, agentRecordResponse) + await(agentReferenceRepo.create(agentReferenceRecord)) + + val requestBody = Json.obj("uid" -> uid, "serviceKeys" -> Json.arr("HMRC-MTD-IT")) + val result = doAgentPostRequest(fakeRequest.uri, requestBody) + result.status shouldBe 404 } diff --git a/it/test/uk/gov/hmrc/agentclientrelationships/controllers/RelationshipsBaseControllerISpec.scala b/it/test/uk/gov/hmrc/agentclientrelationships/controllers/RelationshipsBaseControllerISpec.scala index 1c85dc14..49b6a0c0 100644 --- a/it/test/uk/gov/hmrc/agentclientrelationships/controllers/RelationshipsBaseControllerISpec.scala +++ b/it/test/uk/gov/hmrc/agentclientrelationships/controllers/RelationshipsBaseControllerISpec.scala @@ -25,7 +25,7 @@ import play.api.libs.json.JsValue import play.api.libs.ws.WSClient import play.api.test.Helpers._ import play.utils.UriEncoding -import uk.gov.hmrc.agentclientrelationships.model.EnrolmentKey +import uk.gov.hmrc.agentclientrelationships.model.{EnrolmentKey => LocalEnrolmentKey} import uk.gov.hmrc.agentclientrelationships.repository.{DeleteRecord, MongoDeleteRecordRepository, MongoRelationshipCopyRecordRepository, SyncStatus} import uk.gov.hmrc.agentclientrelationships.services.MongoRecoveryLockService import uk.gov.hmrc.agentclientrelationships.stubs._ @@ -124,12 +124,24 @@ trait RelationshipsBaseControllerISpec val arnEncoded = UriEncoding.encodePathSegment(arn.value, "UTF-8") val arn2 = Arn("AARN0000004") val arn3 = Arn("AARN0000006") + val existingAgentArn = Arn("AARN0000007") val mtdItId = MtdItId("ABCDEF123456789") - val mtdItEnrolmentKey: EnrolmentKey = EnrolmentKey(Service.MtdIt, mtdItId) - val mtdItSuppEnrolmentKey: EnrolmentKey = EnrolmentKey(Service.MtdItSupp, mtdItId) + val utr = Utr("3087612352") + val urn = Urn("XXTRUST12345678") + val utrUriEncoded: String = UriEncoding.encodePathSegment(utr.value, "UTF-8") + val saUtrType = "SAUTR" + val urnType = "URN" + val cgtRef = CgtRef("XMCGTP123456789") + val pptRef = PptRef("XAPPT0004567890") + val cbcId = CbcId("XACBC1234567890") + val plrId = PlrId("XAPLR2222222222") + val mtdItEnrolmentKey: LocalEnrolmentKey = LocalEnrolmentKey(Service.MtdIt, mtdItId) + val cbcUkEnrolmentKey: LocalEnrolmentKey = + LocalEnrolmentKey(Service.Cbc.id, Seq(Identifier("UTR", utr.value), Identifier("cbcId", cbcId.value))) + val mtdItSuppEnrolmentKey: LocalEnrolmentKey = LocalEnrolmentKey(Service.MtdItSupp, mtdItId) val mtdItIdUriEncoded: String = UriEncoding.encodePathSegment(mtdItId.value, "UTF-8") val vrn = Vrn("101747641") - val vatEnrolmentKey: EnrolmentKey = EnrolmentKey(Service.Vat, vrn) + val vatEnrolmentKey: LocalEnrolmentKey = LocalEnrolmentKey(Service.Vat, vrn) val vrnUriEncoded: String = UriEncoding.encodePathSegment(vrn.value, "UTF-8") val nino = Nino("AB123456C") val mtdItIdType = "MTDITID" @@ -137,21 +149,11 @@ trait RelationshipsBaseControllerISpec val oldAgentCode = "oldAgentCode" val testAgentUser = "testAgentUser" val testAgentGroup = "testAgentGroup" + val testExistingAgentGroup = "testExistingAgentGroup" val STRIDE_ROLE = "maintain agent relationships" val NEW_STRIDE_ROLE = "maintain_agent_relationships" val TERMINATION_STRIDE_ROLE = "caat" - val utr = Utr("3087612352") - val urn = Urn("XXTRUST12345678") - val utrUriEncoded: String = UriEncoding.encodePathSegment(utr.value, "UTF-8") - val saUtrType = "SAUTR" - val urnType = "URN" - - val cgtRef = CgtRef("XMCGTP123456789") - - val pptRef = PptRef("XAPPT0004567890") - val cbcId = CbcId("XACBC1234567890") - val plrId = PlrId("XAPLR2222222222") val otherTaxIdentifier: TaxIdentifier => TaxIdentifier = { case MtdItId(_) => MtdItId("ABCDE1234567890") case Vrn(_) => Vrn("101747641") @@ -179,7 +181,7 @@ trait RelationshipsBaseControllerISpec ) = await(deleteRecordRepository.findBy(arn, mtdItEnrolmentKey)) should matchPattern { case Some(DeleteRecord(arn.value, Some(ek), _, _, _, `etmpStatus`, `esStatus`, _, _, _, _)) - if ek == EnrolmentKey(Service.MtdIt, MtdItId("ABCDEF123456789")) => + if ek == LocalEnrolmentKey(Service.MtdIt, MtdItId("ABCDEF123456789")) => } protected def verifyDeleteRecordNotExists = diff --git a/it/test/uk/gov/hmrc/agentclientrelationships/stubs/AuthStub.scala b/it/test/uk/gov/hmrc/agentclientrelationships/stubs/AuthStub.scala index 479d5b05..dbbfd435 100644 --- a/it/test/uk/gov/hmrc/agentclientrelationships/stubs/AuthStub.scala +++ b/it/test/uk/gov/hmrc/agentclientrelationships/stubs/AuthStub.scala @@ -18,9 +18,10 @@ package uk.gov.hmrc.agentclientrelationships.stubs import com.github.tomakehurst.wiremock.client.WireMock._ import com.github.tomakehurst.wiremock.stubbing.StubMapping +import play.api.libs.json.Json import play.api.test.FakeRequest -import uk.gov.hmrc.agentmtdidentifiers.model._ import uk.gov.hmrc.agentclientrelationships.support.WireMockSupport +import uk.gov.hmrc.agentmtdidentifiers.model._ import uk.gov.hmrc.domain.{Nino, TaxIdentifier} import uk.gov.hmrc.http.SessionKeys @@ -436,6 +437,50 @@ trait AuthStub { request.withHeaders(SessionKeys.authToken -> "Bearer XYZ") } + def givenAuthorisedAsAltItsaClient[A]( + request: FakeRequest[A], + nino: Nino + ): FakeRequest[A] = { + givenAuthorisedFor( + Json.obj("retrieve" -> Json.arr("allEnrolments")).toString, + Json + .obj( + "allEnrolments" -> Json.arr( + Json.obj( + "key" -> "HMRC-PT", + "identifiers" -> Json.arr(Json.obj("key" -> "NINO", "value" -> nino.value)) + ) + ) + ) + .toString + ) + + request.withHeaders(SessionKeys.authToken -> "Bearer XYZ") + } + + def givenAuthorisedAsCbcUkClient[A]( + request: FakeRequest[A], + utr: Utr, + cbcId: CbcId + ): FakeRequest[A] = { + givenAuthorisedFor( + Json.obj("retrieve" -> Json.arr("allEnrolments")).toString, + Json + .obj( + "allEnrolments" -> Json.arr( + Json.obj( + "key" -> "HMRC-CBC-ORG", + "identifiers" -> Json + .arr(Json.obj("key" -> "UTR", "value" -> utr.value), Json.obj("key" -> "cbcId", "value" -> cbcId.value)) + ) + ) + ) + .toString + ) + + request.withHeaders(SessionKeys.authToken -> "Bearer XYZ") + } + def givenAuthorisedAsValidAgent[A](request: FakeRequest[A], arn: String) = authenticatedAgent(request, Enrolment("HMRC-AS-AGENT", "AgentReferenceNumber", arn)) diff --git a/it/test/uk/gov/hmrc/agentclientrelationships/stubs/EnrolmentStoreProxyStubs.scala b/it/test/uk/gov/hmrc/agentclientrelationships/stubs/EnrolmentStoreProxyStubs.scala index 8030aa6f..b409fc4b 100644 --- a/it/test/uk/gov/hmrc/agentclientrelationships/stubs/EnrolmentStoreProxyStubs.scala +++ b/it/test/uk/gov/hmrc/agentclientrelationships/stubs/EnrolmentStoreProxyStubs.scala @@ -27,6 +27,8 @@ import uk.gov.hmrc.agentclientrelationships.model.EnrolmentKey import uk.gov.hmrc.agentmtdidentifiers.model._ import uk.gov.hmrc.domain.TaxIdentifier +import java.net.URL + trait EnrolmentStoreProxyStubs extends Eventually { private implicit val patience: PatienceConfig = PatienceConfig(scaled(Span(2, Seconds)), scaled(Span(500, Millis))) @@ -73,7 +75,11 @@ trait EnrolmentStoreProxyStubs extends Eventually { def givenDelegatedGroupIdsExistFor(enrolmentKey: EnrolmentKey, groupIds: Set[String]): StubMapping = stubFor( - get(urlEqualTo(s"$esBaseUrl/enrolments/${enrolmentKey.tag}/groups?type=delegated")) + get( + urlEqualTo( + s"$esBaseUrl/enrolments/${enrolmentKey.tag}/groups?type=delegated" + ) + ) .willReturn( aResponse() .withBody(s""" @@ -319,6 +325,40 @@ trait EnrolmentStoreProxyStubs extends Eventually { def givenCbcNonUkDoesNotExistInES(cbcId: CbcId): StubMapping = givenKnownFactsQuery(Service.CbcNonUk, cbcId, None) + def givenGetAgentReferenceNumberFor(groupId: String, arn: String): StubMapping = + stubFor( + get( + urlEqualTo( + s"$esBaseUrl/groups/$groupId/enrolments?type=principal&service=HMRC-AS-AGENT" + ) + ) + .willReturn( + aResponse() + .withStatus(200) + .withBody(s""" + |{ + | "startRecord": 1, + | "totalRecords": 1, + | "enrolments": [ + | { + | "service": "HMRC-AS-AGENT", + | "friendlyName": "anyName", + | "enrolmentDate": "2018-10-05T14:48:00.000Z", + | "failedActivationCount": 1, + | "activationDate": "2018-10-13T17:36:00.000Z", + | "identifiers": [ + | { + | "key": "AgentReferenceNumber", + | "value": "$arn" + | } + | ] + | } + | ] + |} + """.stripMargin) + ) + ) + private def similarToJson(value: String) = equalToJson(value.stripMargin, true, true) } diff --git a/it/test/uk/gov/hmrc/agentclientrelationships/support/TestData.scala b/it/test/uk/gov/hmrc/agentclientrelationships/support/TestData.scala index 5b490260..cbdfdce1 100644 --- a/it/test/uk/gov/hmrc/agentclientrelationships/support/TestData.scala +++ b/it/test/uk/gov/hmrc/agentclientrelationships/support/TestData.scala @@ -67,12 +67,25 @@ trait TestData { Some(TestBusinessAddress("25 Any Street", Some("Central Grange"), Some("Telford"), None, Some("TF4 3TR"), "GB")) ) + val existingAgencyDetailsResponse: TestAgencyDetails = TestAgencyDetails( + Some("ExistingAgent"), + Some("abc@example.com"), + Some("07345678901"), + Some(TestBusinessAddress("25 Any Street", Some("Central Grange"), Some("Telford"), None, Some("TF4 3TR"), "GB")) + ) + val agentRecordResponse: TestAgentDetailsDesResponse = TestAgentDetailsDesResponse( uniqueTaxReference = Some(Utr("0123456789")), agencyDetails = Some(agencyDetailsResponse), suspensionDetails = Some(suspensionDetails) ) + val existingAgentRecordResponse: TestAgentDetailsDesResponse = TestAgentDetailsDesResponse( + uniqueTaxReference = Some(Utr("0123456989")), + agencyDetails = Some(existingAgencyDetailsResponse), + suspensionDetails = Some(suspensionDetails) + ) + val agentRecordResponseWithNoAgentName: TestAgentDetailsDesResponse = TestAgentDetailsDesResponse( uniqueTaxReference = Some(Utr("0123456789")), agencyDetails = Some(agencyDetailsResponse.copy(agencyName = None)), @@ -90,11 +103,21 @@ trait TestData { "abc@abc.com" ) + val existingAgentDetails: AgencyDetails = AgencyDetails( + "ExistingAgent", + "abc@abc.com" + ) + val agentRecord: AgentDetailsDesResponse = AgentDetailsDesResponse( agencyDetails = agentDetails, suspensionDetails = Some(suspensionDetails) ) + val existingAgentRecord: AgentDetailsDesResponse = AgentDetailsDesResponse( + agencyDetails = existingAgentDetails, + suspensionDetails = Some(suspensionDetails) + ) + val suspendedAgentRecordOption: AgentDetailsDesResponse = AgentDetailsDesResponse( agencyDetails = agentDetails, suspensionDetails = Some(suspensionDetailsSuspended) diff --git a/test/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ValidateInvitationResponseSpec.scala b/test/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ValidateInvitationResponseSpec.scala index eebd744e..365e9e5d 100644 --- a/test/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ValidateInvitationResponseSpec.scala +++ b/test/uk/gov/hmrc/agentclientrelationships/model/invitationLink/ValidateInvitationResponseSpec.scala @@ -34,7 +34,8 @@ class ValidateInvitationResponseSpec extends UnitSpec { "HMRC-MTD-IT", "ABC Accountants", Pending, - Instant.parse("2020-01-01T00:00:00Z") + Instant.parse("2020-01-01T00:00:00Z"), + None ) val json = Json.obj( diff --git a/test/uk/gov/hmrc/agentclientrelationships/services/CheckRelationshipServiceSpec.scala b/test/uk/gov/hmrc/agentclientrelationships/services/CheckRelationshipServiceSpec.scala index dba83027..7f3ff9ab 100644 --- a/test/uk/gov/hmrc/agentclientrelationships/services/CheckRelationshipServiceSpec.scala +++ b/test/uk/gov/hmrc/agentclientrelationships/services/CheckRelationshipServiceSpec.scala @@ -25,7 +25,7 @@ import org.scalatest.freespec.AnyFreeSpecLike import org.scalatest.matchers.should.Matchers import uk.gov.hmrc.agentclientrelationships.connectors._ import uk.gov.hmrc.agentclientrelationships.model.{EnrolmentKey, UserId} -import uk.gov.hmrc.agentclientrelationships.repository.{SyncStatus => _} +import uk.gov.hmrc.agentclientrelationships.repository.{PartialAuthRepository, SyncStatus => _} import uk.gov.hmrc.agentclientrelationships.support.ResettingMockitoSugar import uk.gov.hmrc.agentmtdidentifiers.model.{Arn, Enrolment, Identifier, Vrn} import uk.gov.hmrc.domain._ @@ -52,6 +52,10 @@ class CheckRelationshipServiceSpec val agentCode: AgentCode = AgentCode("ABC1234") val metrics = mock[Metrics] + private val mockPartialAuthRepo = mock[PartialAuthRepository] + private val mockAgentAssuranceConnector = mock[AgentAssuranceConnector] + private val mockIfConnector = mock[IFConnector] + private val mockAgentFiConnector = mock[AgentFiRelationshipConnector] implicit val hc: HeaderCarrier = HeaderCarrier() @@ -74,7 +78,16 @@ class CheckRelationshipServiceSpec when(gs.getGroupUsers(any[String])(any[HeaderCarrier])) .thenReturn(Future.successful(Seq(UserDetails(userId = Some(userId.value))))) - val crs = new CheckRelationshipsService(es, ap, gs, metrics) + val crs = new CheckRelationshipsService( + es, + ap, + mockAgentAssuranceConnector, + mockIfConnector, + gs, + mockPartialAuthRepo, + mockAgentFiConnector, + metrics + ) crs.checkForRelationship(arn, Some(userId), enrolmentKey).futureValue shouldBe true } "should return 404 if the client is in at least an access groups but the user has not been assigned the client" in { @@ -94,7 +107,16 @@ class CheckRelationshipServiceSpec when(gs.getGroupUsers(any[String])(any[HeaderCarrier])) .thenReturn(Future.successful(Seq(UserDetails(userId = Some(userId.value))))) - val crs = new CheckRelationshipsService(es, ap, gs, metrics) + val crs = new CheckRelationshipsService( + es, + ap, + mockAgentAssuranceConnector, + mockIfConnector, + gs, + mockPartialAuthRepo, + mockAgentFiConnector, + metrics + ) crs.checkForRelationship(arn, Some(userId), enrolmentKey).futureValue shouldBe false } "should return 200 if the client is in at least an access groups and the user has been assigned the client" in { @@ -114,7 +136,16 @@ class CheckRelationshipServiceSpec when(gs.getGroupUsers(any[String])(any[HeaderCarrier])) .thenReturn(Future.successful(Seq(UserDetails(userId = Some(userId.value))))) - val crs = new CheckRelationshipsService(es, ap, gs, metrics) + val crs = new CheckRelationshipsService( + es, + ap, + mockAgentAssuranceConnector, + mockIfConnector, + gs, + mockPartialAuthRepo, + mockAgentFiConnector, + metrics + ) crs.checkForRelationship(arn, Some(userId), enrolmentKey).futureValue shouldBe true } } @@ -130,7 +161,16 @@ class CheckRelationshipServiceSpec when(gs.getGroupUsers(any[String])(any[HeaderCarrier])) .thenReturn(Future.successful(Seq(UserDetails(userId = Some(userId.value))))) - val relationshipsService = new CheckRelationshipsService(es, ap, gs, metrics) + val relationshipsService = new CheckRelationshipsService( + es, + ap, + mockAgentAssuranceConnector, + mockIfConnector, + gs, + mockPartialAuthRepo, + mockAgentFiConnector, + metrics + ) relationshipsService.checkForRelationship(arn, Some(userId), enrolmentKey).futureValue shouldBe false } } @@ -152,7 +192,16 @@ class CheckRelationshipServiceSpec when(gs.getGroupUsers(any[String])(any[HeaderCarrier])) .thenReturn(Future.successful(Seq(UserDetails(userId = Some("someOtherUserId"))))) - val crs = new CheckRelationshipsService(es, ap, gs, metrics) + val crs = new CheckRelationshipsService( + es, + ap, + mockAgentAssuranceConnector, + mockIfConnector, + gs, + mockPartialAuthRepo, + mockAgentFiConnector, + metrics + ) crs.checkForRelationship(arn, Some(userId), enrolmentKey).futureValue shouldBe false } } @@ -171,7 +220,16 @@ class CheckRelationshipServiceSpec when(gs.getGroupUsers(any[String])(any[HeaderCarrier])) .thenReturn(Future.successful(Seq(UserDetails(userId = Some(userId.value))))) - val crs = new CheckRelationshipsService(es, ap, gs, metrics) + val crs = new CheckRelationshipsService( + es, + ap, + mockAgentAssuranceConnector, + mockIfConnector, + gs, + mockPartialAuthRepo, + mockAgentFiConnector, + metrics + ) crs.checkForRelationship(arn, None, enrolmentKey).futureValue shouldBe true } } @@ -187,7 +245,16 @@ class CheckRelationshipServiceSpec when(gs.getGroupUsers(any[String])(any[HeaderCarrier])) .thenReturn(Future.successful(Seq(UserDetails(userId = Some(userId.value))))) - val relationshipsService = new CheckRelationshipsService(es, ap, gs, metrics) + val relationshipsService = new CheckRelationshipsService( + es, + ap, + mockAgentAssuranceConnector, + mockIfConnector, + gs, + mockPartialAuthRepo, + mockAgentFiConnector, + metrics + ) relationshipsService.checkForRelationship(arn, None, enrolmentKey).futureValue shouldBe false } }