diff --git a/app/uk/gov/hmrc/agentregistrationrisking/audit/AuditEvent.scala b/app/uk/gov/hmrc/agentregistrationrisking/audit/AuditEvent.scala index 81b9b06..b6346e8 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/audit/AuditEvent.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/audit/AuditEvent.scala @@ -22,8 +22,8 @@ import play.api.libs.json.OFormat import play.api.libs.json.OWrites import uk.gov.hmrc.agentregistration.shared.ApplicationReference import uk.gov.hmrc.agentregistration.shared.PersonReference -import uk.gov.hmrc.agentregistration.shared.risking.RiskingOutcome import uk.gov.hmrc.agentregistration.shared.util.JsonFormatsFactory +import uk.gov.hmrc.agentregistrationrisking.model.RiskingOutcome sealed trait AuditEvent: diff --git a/app/uk/gov/hmrc/agentregistrationrisking/config/AppConfig.scala b/app/uk/gov/hmrc/agentregistrationrisking/config/AppConfig.scala index 27900b7..72336aa 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/config/AppConfig.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/config/AppConfig.scala @@ -42,6 +42,7 @@ class AppConfig @Inject() ( def getConfString(key: String): String = servicesConfig.getConfString(key, throw new RuntimeException(s"config '$key' not found")) val appName: String = config.get[String]("appName") + val emailBaseUrl: String = servicesConfig.baseUrl("email") val enrolmentStoreProxyBaseUrl: String = servicesConfig.baseUrl("enrolment-store-proxy") val hmrcAsAgentEnrolment: Enrolment = Enrolment(key = "HMRC-AS-AGENT") val hipBaseUrl: String = servicesConfig.baseUrl("hip") diff --git a/app/uk/gov/hmrc/agentregistrationrisking/connectors/EmailConnector.scala b/app/uk/gov/hmrc/agentregistrationrisking/connectors/EmailConnector.scala new file mode 100644 index 0000000..38b8ad4 --- /dev/null +++ b/app/uk/gov/hmrc/agentregistrationrisking/connectors/EmailConnector.scala @@ -0,0 +1,56 @@ +/* + * Copyright 2026 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.agentregistrationrisking.connectors + +import play.api.http.Status.ACCEPTED +import uk.gov.hmrc.agentregistration.shared.util.Errors +import uk.gov.hmrc.agentregistrationrisking.config.AppConfig +import uk.gov.hmrc.agentregistrationrisking.model.SendEmailRequest +import uk.gov.hmrc.agentregistrationrisking.util.FutureUtil.andLogOnFailure +import uk.gov.hmrc.http.client.HttpClientV2 + +import javax.inject.Inject +import javax.inject.Singleton +import scala.concurrent.ExecutionContext + +@Singleton +class EmailConnector @Inject() ( + appConfig: AppConfig, + httpClient: HttpClientV2 +)(using ExecutionContext) +extends Connector: + + def sendEmail(emailInformation: SendEmailRequest)(using RequestHeader): Future[Unit] = + val url: URL = url"$baseUrl/hmrc/email" + httpClient + .post(url) + .withBody(Json.toJson(emailInformation)) + .execute[HttpResponse] + .map: response => + response.status match + case ACCEPTED => () + case status => + Errors.throwUpstreamErrorResponse( + httpMethod = "POST", + url = url, + status = status, + response = response, + info = s"Failed to send email for template ${emailInformation.templateId}" + ) + .andLogOnFailure(s"Failed to send email for template ${emailInformation.templateId}") + + private val baseUrl: String = appConfig.emailBaseUrl diff --git a/app/uk/gov/hmrc/agentregistrationrisking/controllers/RiskingProgressController.scala b/app/uk/gov/hmrc/agentregistrationrisking/controllers/RiskingProgressController.scala index bfc4d8f..3578885 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/controllers/RiskingProgressController.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/controllers/RiskingProgressController.scala @@ -22,14 +22,13 @@ import play.api.mvc.AnyContent import play.api.mvc.ControllerComponents import uk.gov.hmrc.agentregistration.shared.ApplicationReference import uk.gov.hmrc.agentregistration.shared.PersonReference -import uk.gov.hmrc.agentregistration.shared.risking.RiskingOutcome.* import uk.gov.hmrc.agentregistration.shared.risking.* import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress.ReceivedRiskingResults import uk.gov.hmrc.agentregistrationrisking.action.Actions import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName -import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo -import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.model.RiskingOutcome +import uk.gov.hmrc.agentregistrationrisking.model.RiskingOutcome.* import uk.gov.hmrc.agentregistrationrisking.services.ApplicationForRiskingService import uk.gov.hmrc.agentregistrationrisking.services.RiskingOutcomeHelper @@ -40,8 +39,6 @@ import scala.concurrent.ExecutionContext class RiskingProgressController @Inject() ( actions: Actions, cc: ControllerComponents, - applicationForRiskingRepo: ApplicationForRiskingRepo, - individualForRiskingRepo: IndividualForRiskingRepo, applicationForRiskingService: ApplicationForRiskingService )(using ExecutionContext) extends BackendController(cc): diff --git a/app/uk/gov/hmrc/agentregistrationrisking/controllers/SdesNotificationController.scala b/app/uk/gov/hmrc/agentregistrationrisking/controllers/SdesNotificationController.scala index ef10719..212fdf3 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/controllers/SdesNotificationController.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/controllers/SdesNotificationController.scala @@ -20,21 +20,25 @@ import com.google.inject.Inject import play.api.mvc.Action import play.api.mvc.ControllerComponents import play.api.mvc.RequestHeader - -import scala.concurrent.Future -import scala.concurrent.ExecutionContext import uk.gov.hmrc.agentregistrationrisking.action.Actions import uk.gov.hmrc.agentregistrationrisking.model.sdes.* +import uk.gov.hmrc.agentregistrationrisking.services.ApplicationOutcomeService +import uk.gov.hmrc.agentregistrationrisking.services.EmailServiceForApprovedApplications +import uk.gov.hmrc.agentregistrationrisking.services.EmailServiceForFailedNonFixable import uk.gov.hmrc.agentregistrationrisking.services.RiskingResultsService -import uk.gov.hmrc.agentregistrationrisking.services.SdesProxyService import uk.gov.hmrc.agentregistrationrisking.services.SubscriptionService -import uk.gov.hmrc.agentregistrationrisking.util.ProcessInSequence + +import scala.concurrent.ExecutionContext +import scala.concurrent.Future class SdesNotificationController @Inject() ( cc: ControllerComponents, actions: Actions, riskingResultsService: RiskingResultsService, - subscriptionService: SubscriptionService + applicationOutcomeService: ApplicationOutcomeService, + subscriptionService: SubscriptionService, + emailServiceForApprovedApplications: EmailServiceForApprovedApplications, + emailServiceForFailedNonFixable: EmailServiceForFailedNonFixable )(using ExecutionContext) extends BackendController(cc): @@ -58,5 +62,8 @@ extends BackendController(cc): private def onFileReady()(using RequestHeader): Future[Unit] = (for _ <- riskingResultsService.processResultsFiles() - _ <- subscriptionService.subscribeApprovedApplications() + _ <- applicationOutcomeService.processOverallOutcomes() + _ <- subscriptionService.processSubscriptions() + _ <- emailServiceForApprovedApplications.processEmails() + _ <- emailServiceForFailedNonFixable.processEmails() yield ()).recover { case ex: Exception => logger.error(s"Error processing file ready notification", ex) } diff --git a/app/uk/gov/hmrc/agentregistrationrisking/controllers/SubmitForRiskingController.scala b/app/uk/gov/hmrc/agentregistrationrisking/controllers/SubmitForRiskingController.scala index 1cf06fb..a1c92c8 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/controllers/SubmitForRiskingController.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/controllers/SubmitForRiskingController.scala @@ -25,6 +25,7 @@ import uk.gov.hmrc.agentregistration.shared.risking.SubmitForRiskingRequest import uk.gov.hmrc.agentregistrationrisking.action.Actions import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.model.OverallStatus import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo @@ -69,7 +70,11 @@ extends BackendController(cc): lastUpdatedAt = createdAt, entityRiskingResult = None, isSubscribed = false, - isEmailSent = false + isEmailSent = false, + overallStatus = OverallStatus( + riskingOutcome = None, + emailsProcessed = false + ) ) private def makeIndividualForRiskingList( @@ -93,5 +98,6 @@ extends BackendController(cc): individualProvidedDetails = individualProvidedDetails, createdAt = createdAt, lastUpdatedAt = createdAt, - individualRiskingResult = None + individualRiskingResult = None, + isEmailSent = false ) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/model/ApplicationForRisking.scala b/app/uk/gov/hmrc/agentregistrationrisking/model/ApplicationForRisking.scala index 459c4dc..790f788 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/model/ApplicationForRisking.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/model/ApplicationForRisking.scala @@ -31,7 +31,8 @@ final case class ApplicationForRisking( lastUpdatedAt: Instant, entityRiskingResult: Option[EntityRiskingResult], isSubscribed: Boolean, - isEmailSent: Boolean + isEmailSent: Boolean, + overallStatus: OverallStatus ) object ApplicationForRisking: diff --git a/app/uk/gov/hmrc/agentregistrationrisking/model/EmailTemplateId.scala b/app/uk/gov/hmrc/agentregistrationrisking/model/EmailTemplateId.scala new file mode 100644 index 0000000..97516b7 --- /dev/null +++ b/app/uk/gov/hmrc/agentregistrationrisking/model/EmailTemplateId.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.model + +import play.api.libs.json.JsString +import play.api.libs.json.Writes + +enum EmailTemplateId(val id: String): + + case RegistrationSuccess + extends EmailTemplateId("agent_registration_success") + case ApplicationNonFixableFailure + extends EmailTemplateId("agent_registration_application_non_fixable_failure") + case IndividualNonFixableFailure + extends EmailTemplateId("agent_registration_individual_non_fixable_failure") + +object EmailTemplateId: + + given Writes[EmailTemplateId] = Writes(o => JsString(o.id)) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/model/IndividualForRisking.scala b/app/uk/gov/hmrc/agentregistrationrisking/model/IndividualForRisking.scala index 2108d25..d2173df 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/model/IndividualForRisking.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/model/IndividualForRisking.scala @@ -32,7 +32,8 @@ final case class IndividualForRisking( individualProvidedDetails: IndividualProvidedDetails, createdAt: Instant, lastUpdatedAt: Instant, - individualRiskingResult: Option[IndividualRiskingResult] + individualRiskingResult: Option[IndividualRiskingResult], + isEmailSent: Boolean ): // values that we do not store at the moment diff --git a/app/uk/gov/hmrc/agentregistrationrisking/model/OverallStatus.scala b/app/uk/gov/hmrc/agentregistrationrisking/model/OverallStatus.scala new file mode 100644 index 0000000..8b5dfbe --- /dev/null +++ b/app/uk/gov/hmrc/agentregistrationrisking/model/OverallStatus.scala @@ -0,0 +1,35 @@ +/* + * Copyright 2026 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.agentregistrationrisking.model + +import play.api.libs.json.Json +import play.api.libs.json.OFormat + +/** Represents the overall status for the [[ApplicationForRisking]] class. + * + * @param riskingOutcome + * the outcome of the risking process + * @param emailsProcessed + * whether all required emails have been sent for this application after computed riskingOutcome + */ +final case class OverallStatus( + riskingOutcome: Option[RiskingOutcome], + emailsProcessed: Boolean +) + +object OverallStatus: + given OFormat[OverallStatus] = Json.format[OverallStatus] diff --git a/app/uk/gov/hmrc/agentregistration/shared/risking/RiskingOutcome.scala b/app/uk/gov/hmrc/agentregistrationrisking/model/RiskingOutcome.scala similarity index 71% rename from app/uk/gov/hmrc/agentregistration/shared/risking/RiskingOutcome.scala rename to app/uk/gov/hmrc/agentregistrationrisking/model/RiskingOutcome.scala index 5863a44..bdaefad 100644 --- a/app/uk/gov/hmrc/agentregistration/shared/risking/RiskingOutcome.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/model/RiskingOutcome.scala @@ -14,10 +14,16 @@ * limitations under the License. */ -package uk.gov.hmrc.agentregistration.shared.risking +package uk.gov.hmrc.agentregistrationrisking.model + +import play.api.libs.json.Format +import uk.gov.hmrc.agentregistration.shared.util.JsonFormatsFactory enum RiskingOutcome: + case Approved case FailedNonFixable case FailedFixable - case Approved + +object RiskingOutcome: + given format: Format[RiskingOutcome] = JsonFormatsFactory.makeEnumFormat[RiskingOutcome] diff --git a/app/uk/gov/hmrc/agentregistrationrisking/model/SendEmailRequest.scala b/app/uk/gov/hmrc/agentregistrationrisking/model/SendEmailRequest.scala new file mode 100644 index 0000000..5845989 --- /dev/null +++ b/app/uk/gov/hmrc/agentregistrationrisking/model/SendEmailRequest.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2026 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.agentregistrationrisking.model + +import play.api.libs.json.Json +import play.api.libs.json.OWrites +import uk.gov.hmrc.agentregistration.shared.EmailAddress + +final case class SendEmailRequest( + to: Seq[EmailAddress], + templateId: EmailTemplateId, + parameters: Map[String, String] +) + +object SendEmailRequest: + given OWrites[SendEmailRequest] = Json.writes[SendEmailRequest] diff --git a/app/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepo.scala b/app/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepo.scala index 6af14ca..55bbfaa 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepo.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepo.scala @@ -16,36 +16,29 @@ package uk.gov.hmrc.agentregistrationrisking.repository -import org.mongodb.scala.model.Aggregates -import org.mongodb.scala.model.Filters -import org.mongodb.scala.model.IndexModel -import org.mongodb.scala.model.IndexOptions -import org.mongodb.scala.model.Indexes -import org.mongodb.scala.model.Updates +import org.bson.json.JsonMode +import org.bson.json.JsonWriterSettings +import org.mongodb.scala.Document +import org.mongodb.scala.bson.conversions.Bson +import org.mongodb.scala.model.* +import play.api.libs.json.Json +import uk.gov.hmrc.agentregistration.shared.ApplicationReference +import uk.gov.hmrc.agentregistrationrisking.config.AppConfig +import uk.gov.hmrc.agentregistrationrisking.model.* +import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepoHelp.given +import uk.gov.hmrc.agentregistrationrisking.repository.Repo.IdExtractor +import uk.gov.hmrc.agentregistrationrisking.repository.Repo.IdString +import uk.gov.hmrc.agentregistrationrisking.repository.Repo.toBison import uk.gov.hmrc.mongo.MongoComponent import uk.gov.hmrc.mongo.play.json.Codecs +import java.time.Clock import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.concurrent.duration.FiniteDuration -import ApplicationForRiskingRepoHelp.given -import org.mongodb.scala.result.UpdateResult -import com.mongodb.client.model.Field -import uk.gov.hmrc.agentregistration.shared.ApplicationReference -import uk.gov.hmrc.agentregistration.shared.PersonReference -import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.=== -import uk.gov.hmrc.agentregistrationrisking.config.AppConfig -import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking -import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking -import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName -import uk.gov.hmrc.agentregistrationrisking.repository.Repo.IdExtractor -import uk.gov.hmrc.agentregistrationrisking.repository.Repo.IdString - -import java.time.Clock -import java.time.Instant @Singleton final class ApplicationForRiskingRepo @Inject() ( @@ -57,44 +50,120 @@ extends Repo[ApplicationReference, ApplicationForRisking]( collectionName = "application-for-risking", mongoComponent = mongoComponent, indexes = ApplicationForRiskingRepoHelp.indexes(appConfig.ApplicationForRiskingRepo.ttl), - extraCodecs = Seq(Codecs.playFormatCodec(ApplicationForRisking.format)), + extraCodecs = Seq( + Codecs.playFormatCodec(ApplicationForRisking.format), + Codecs.playFormatCodec(RiskingOutcome.format) + ), replaceIndexes = true ): + // ═══════════════════════════════════════════════════════════════════════════════ + // CRITICAL: ALL QUERIES MUST BE TESTED IN REPOSITORY SPEC + // Untested queries can cause Production data corruption/loss and Difficult recovery !!!!!!!!!! + // ═══════════════════════════════════════════════════════════════════════════════ + def findReadyForSubmission(): Future[Seq[ApplicationForRisking]] = collection .find(Filters.exists(FieldNames.riskingFileName, false)) // ready for submissions don't have set riskingFileId .toFuture() - // TODO needs testing? - def updateRiskingFileId( + def findReadyToBeSubscribed(): Future[Seq[ApplicationForRisking]] = collection + .find( + Filters.and( + Filters.eq(FieldNames.overallStatus.riskingOutcome, RiskingOutcome.Approved.toBison), + Filters.eq(FieldNames.isSubscribed, false) + ) + ) + .toFuture() + + def findReadyToSetRiskingOutcome(): Future[Seq[ApplicationWithIndividuals]] = findApplicationWithIndividuals( + applicationFilter = Filters.and( + Filters.exists(FieldNames.entityRiskingResult), + Filters.exists(FieldNames.overallStatus.riskingOutcome, false) + ), + individualForAllFilter = Filters.exists(FieldNames.individualRiskingResult) + ) + + // ═══════════════════════════════════════════════════════════════════════════════ + // CRITICAL: ALL QUERIES MUST BE TESTED IN REPOSITORY SPEC + // Untested queries can cause Production data corruption/loss and Difficult recovery !!!!!!!!!! + // ═══════════════════════════════════════════════════════════════════════════════ + + def findRequiringEmailProcessingForFailedNonFixable(): Future[Seq[ApplicationWithIndividuals]] = findApplicationWithIndividuals( + applicationFilter = Filters.and( + Filters.eq(FieldNames.overallStatus.riskingOutcome, RiskingOutcome.FailedNonFixable.toBison), + Filters.eq(FieldNames.overallStatus.emailsProcessed, false) + ) + ) + + def findApplicationsAwaitingOverallOutcome(): Future[Seq[ApplicationWithIndividuals]] = findApplicationWithIndividuals( + applicationFilter = Filters.and( + Filters.exists(FieldNames.entityRiskingResult), + Filters.exists(FieldNames.overallStatus.riskingOutcome, false) + ), + individualForAllFilter = Filters.exists(FieldNames.individualRiskingResult) + ) + + // ═══════════════════════════════════════════════════════════════════════════════ + // CRITICAL: ALL QUERIES MUST BE TESTED IN REPOSITORY SPEC + // Untested queries can cause Production data corruption/loss and Difficult recovery !!!!!!!!!! + // ═══════════════════════════════════════════════════════════════════════════════ + + private val relaxedJson: JsonWriterSettings = JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build() + + private def findApplicationWithIndividuals( + applicationFilter: Bson, + individualForAllFilter: Bson = Filters.empty() // the filter must apply "forall" individuals otherwise entire ApplicationWithIndividuals is discarded + ): Future[Seq[ApplicationWithIndividuals]] = collection + .aggregate[Document](Seq( + Aggregates.filter(applicationFilter), + Aggregates.lookup( + from = IndividualForRiskingRepo.collectionName, + localField = FieldNames.applicationReference, + foreignField = FieldNames.applicationReference, + as = "individuals" + ), + Aggregates.filter(Repo.forall("individuals", individualForAllFilter)) + )) + .toFuture() + .map: + _.map: (doc: Document) => + val json = Json.parse(doc.toJson(relaxedJson)) + val app = json.as[ApplicationForRisking] + val individuals = (json \ "individuals").as[Seq[IndividualForRisking]] + ApplicationWithIndividuals(app, individuals) + + def updateRiskingFileName( applicationReferences: Seq[ApplicationReference], riskingFileName: RiskingFileName - ): Future[UpdateResult] = collection + ): Future[Unit] = collection .updateMany( Filters.in(FieldNames.applicationReference, applicationReferences.map(_.value)*), Updates.combine( - Updates.set(FieldNames.riskingFileName, riskingFileName.value), - Updates.set(FieldNames.lastUpdatedAt, Instant.now(clock).toString) + Updates.set(FieldNames.riskingFileName, riskingFileName.value) ) - ).toFuture() + ) + .toFuture() + .map(_ => ()) + + // ═══════════════════════════════════════════════════════════════════════════════ + // CRITICAL: ALL QUERIES MUST BE TESTED IN REPOSITORY SPEC + // Untested queries can cause Production data corruption/loss and Difficult recovery !!!!!!!!!! + // ═══════════════════════════════════════════════════════════════════════════════ -// def findReadyForSubscription(): Future[Seq[ApplicationForRisking]] = collection -// .find( -// Filters.and( -// Filters.size("failures", 0), -// Filters.eq("isSubscribed", false) -// ) -// ).toFuture() - - def findNotSubscribedWithResults(): Future[Seq[ApplicationForRisking]] = { - collection - .find( - Filters.and( - Filters.exists(FieldNames.entityRiskingResult), - Filters.eq(FieldNames.isSubscribed, false) - ) - ).toFuture() - } + def findByRiskingFileName( + riskingFileName: RiskingFileName + ): Future[Seq[ApplicationForRisking]] = collection + .find(Filters.eq(FieldNames.riskingFileName, riskingFileName.value)) + .toFuture() + + def findSubscribedReadyForSuccessEmail(): Future[Seq[ApplicationForRisking]] = collection + .find( + Filters.and( + Filters.eq(FieldNames.overallStatus.riskingOutcome, RiskingOutcome.Approved.toBison), + Filters.eq(FieldNames.isSubscribed, true), + Filters.eq(FieldNames.isEmailSent, false) + ) + ).toFuture() // when named ApplicationForRiskingRepo, Scala 3 compiler complains // about cyclic reference error during compilation ... @@ -123,9 +192,25 @@ object ApplicationForRiskingRepoHelp: .unique(false) ), IndexModel( - keys = Indexes.ascending(FieldNames.lastUpdatedAt), + keys = Indexes.ascending( + FieldNames.overallStatus.riskingOutcome, + FieldNames.isSubscribed, + FieldNames.isEmailSent + ), + indexOptions = IndexOptions() + .name("overallStatus_riskingOutcome_isSubscribed_isEmailSent_index") + ), + IndexModel( + keys = Indexes.ascending( + FieldNames.overallStatus.riskingOutcome, + FieldNames.overallStatus.emailsProcessed + ), + indexOptions = IndexOptions() + .name("overallStatus_riskingOutcome_index") + ), + IndexModel( + keys = Indexes.ascending(FieldNames.entityRiskingResult), indexOptions = IndexOptions() - .expireAfter(cacheTtl.toSeconds, TimeUnit.SECONDS) - .name(FieldNames.lastUpdatedAtIndex) + .name(FieldNames.entityRiskingResultIndex) ) ) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/repository/FieldNames.scala b/app/uk/gov/hmrc/agentregistrationrisking/repository/FieldNames.scala index a661996..a8292d6 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/repository/FieldNames.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/repository/FieldNames.scala @@ -18,6 +18,11 @@ package uk.gov.hmrc.agentregistrationrisking.repository object FieldNames: + object overallStatus: + + val riskingOutcome: String = "overallStatus.riskingOutcome" + val emailsProcessed: String = "overallStatus.emailsProcessed" + val riskingFileName: String = "riskingFileName" val riskingFileNameIndex: String = riskingFileName + "Index" val uploadedAt: String = "uploadedAt" @@ -33,5 +38,12 @@ object FieldNames: val personReferenceIndex: String = personReference + "Index" val entityRiskingResult: String = "entityRiskingResult" + val entityRiskingResultIndex: String = entityRiskingResult + "Index" + + val individualRiskingResult: String = "individualRiskingResult" val isSubscribed: String = "isSubscribed" + + val isEmailSent: String = "isEmailSent" + + diff --git a/app/uk/gov/hmrc/agentregistrationrisking/repository/InividualForRiskingRepo.scala b/app/uk/gov/hmrc/agentregistrationrisking/repository/InividualForRiskingRepo.scala index 8420a52..f9823ff 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/repository/InividualForRiskingRepo.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/repository/InividualForRiskingRepo.scala @@ -20,9 +20,13 @@ import org.mongodb.scala.model.Filters import org.mongodb.scala.model.IndexModel import org.mongodb.scala.model.IndexOptions import org.mongodb.scala.model.Indexes +import org.mongodb.scala.model.Updates +import org.mongodb.scala.result.UpdateResult import uk.gov.hmrc.mongo.MongoComponent import uk.gov.hmrc.mongo.play.json.Codecs +import java.time.Clock +import java.time.Instant import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -37,13 +41,17 @@ import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking import uk.gov.hmrc.agentregistrationrisking.repository.Repo.IdExtractor import uk.gov.hmrc.agentregistrationrisking.repository.Repo.IdString +object IndividualForRiskingRepo: + val collectionName: String = "individual-for-risking" + @Singleton final class IndividualForRiskingRepo @Inject() ( mongoComponent: MongoComponent, - appConfig: AppConfig + appConfig: AppConfig, + clock: Clock )(using ec: ExecutionContext) extends Repo[PersonReference, IndividualForRisking]( - collectionName = "individual-for-risking", + collectionName = IndividualForRiskingRepo.collectionName, mongoComponent = mongoComponent, indexes = IndividualForRiskingRepoHelp.indexes(appConfig.ApplicationForRiskingRepo.ttl), extraCodecs = Seq(Codecs.playFormatCodec(IndividualForRisking.format)), @@ -64,6 +72,15 @@ extends Repo[PersonReference, IndividualForRisking]( ) .toFuture() + def updateEmailSent(personReference: PersonReference): Future[UpdateResult] = collection + .updateOne( + Filters.eq(FieldNames.personReference, personReference.value), + Updates.combine( + Updates.set(FieldNames.isEmailSent, true), + Updates.set(FieldNames.lastUpdatedAt, Instant.now(clock).toString) + ) + ).toFuture() + object IndividualForRiskingRepoHelp: given IdString[PersonReference] = diff --git a/app/uk/gov/hmrc/agentregistrationrisking/repository/Repo.scala b/app/uk/gov/hmrc/agentregistrationrisking/repository/Repo.scala index f0aeab5..541b867 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/repository/Repo.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/repository/Repo.scala @@ -16,6 +16,7 @@ package uk.gov.hmrc.agentregistrationrisking.repository +import org.bson.BsonValue import org.bson.codecs.Codec import org.mongodb.scala.bson.conversions.Bson import org.mongodb.scala.model.Filters @@ -26,6 +27,7 @@ import play.api.libs.json.* import uk.gov.hmrc.agentregistrationrisking.repository.Repo.IdExtractor import uk.gov.hmrc.agentregistrationrisking.repository.Repo.IdString import uk.gov.hmrc.mongo.MongoComponent +import uk.gov.hmrc.mongo.play.json.Codecs import uk.gov.hmrc.mongo.play.json.PlayMongoRepository import scala.concurrent.ExecutionContext @@ -96,3 +98,38 @@ object Repo: trait IdExtractor[A, ID]: def id(a: A): ID + + /** Returns a filter that matches documents where '''every''' element in the array field satisfies `filter`. + * + * Mirrors the semantics of [[scala.collection.IterableOps.forall]]: + * - empty array → `true` (vacuous truth, same as `List.empty.forall(p)`) + * - all elements satisfy `filter` → `true` + * - any element fails `filter` → the whole document is excluded + * + * ==How it works== + * MongoDB has no `$forall` operator, so the universal quantifier is expressed via De Morgan's law: + * {{{ + * ∀x ∈ array: P(x) ≡ ¬∃x ∈ array: ¬P(x) + * }}} + * Translated to MongoDB operators: + * {{{ + * $nor [ $elemMatch(array, $nor [filter]) ] + * │ └── ¬P(x): element does NOT satisfy filter + * │ └── ∃x: there exists such a failing element + * └── ¬∃x: no such failing element exists → all pass + * }}} + * + * @param fieldName + * name of the array field to test + * @param filter + * condition each element must satisfy + */ + def forall( + fieldName: String, + filter: Bson + ): Bson = Filters.nor( + Filters.elemMatch(fieldName, Filters.nor(filter)) + ) + + extension [A: Writes](a: A) + def toBison: BsonValue = Codecs.toBson(a) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/runner/RiskingRunner.scala b/app/uk/gov/hmrc/agentregistrationrisking/runner/RiskingRunner.scala index bd7e78b..8f12706 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/runner/RiskingRunner.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/runner/RiskingRunner.scala @@ -79,7 +79,7 @@ extends RequestAwareLogging: _ = logger.info(s"Uploaded risking file to object store: ${objectSummary.location}") _ <- riskingFileRepo.upsert(riskingFileWithContent.riskingFile) _ = logger.info(s"Persisted risking file: ${riskingFileWithContent.riskingFile}") - _ <- applicationForRiskingRepo.updateRiskingFileId( + _ <- applicationForRiskingRepo.updateRiskingFileName( applicationReferences = applicationReferences, riskingFileName = riskingFileName ) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/services/ApplicationForRiskingService.scala b/app/uk/gov/hmrc/agentregistrationrisking/services/ApplicationForRiskingService.scala index 8fc4c4b..04c6304 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/services/ApplicationForRiskingService.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/services/ApplicationForRiskingService.scala @@ -19,9 +19,9 @@ package uk.gov.hmrc.agentregistrationrisking.services import play.api.mvc.RequestHeader import uk.gov.hmrc.agentregistration.shared.ApplicationReference import uk.gov.hmrc.agentregistration.shared.PersonReference -import uk.gov.hmrc.agentregistration.shared.risking.RiskingOutcome import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.=== import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.RiskingOutcome import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals import uk.gov.hmrc.agentregistrationrisking.model.RiskingResult import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking diff --git a/app/uk/gov/hmrc/agentregistrationrisking/services/ApplicationOutcomeService.scala b/app/uk/gov/hmrc/agentregistrationrisking/services/ApplicationOutcomeService.scala new file mode 100644 index 0000000..f013ee2 --- /dev/null +++ b/app/uk/gov/hmrc/agentregistrationrisking/services/ApplicationOutcomeService.scala @@ -0,0 +1,67 @@ +/* + * Copyright 2026 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.agentregistrationrisking.services + +import com.softwaremill.quicklens.modify +import play.api.mvc.RequestHeader +import uk.gov.hmrc.agentregistrationrisking.model.* +import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.util.ProcessInSequence +import uk.gov.hmrc.agentregistrationrisking.util.RequestAwareLogging + +import javax.inject.Inject +import javax.inject.Singleton +import scala.concurrent.ExecutionContext +import scala.concurrent.Future + +@Singleton +class ApplicationOutcomeService @Inject() ( + applicationForRiskingRepo: ApplicationForRiskingRepo +)(using + ExecutionContext +) +extends RequestAwareLogging: + + def processOverallOutcomes()(using RequestHeader): Future[Unit] = + for + applicationWithIndividuals: Seq[ApplicationWithIndividuals] <- applicationForRiskingRepo.findReadyToSetRiskingOutcome() + applicationCount: Int = applicationWithIndividuals.size + _ = logger.info(s"Found $applicationCount applications ready to compute overall outcome") + updatedOutcomeCount <- + ProcessInSequence + .processAllInSequence(applicationWithIndividuals)(computeAndSaveOverallOutcome): + case (ex, appWithIndividuals) => + logger.error( + s"Failed to compute overall outcome for application ${appWithIndividuals.application.applicationReference.value}", + ex + ) + _ = logger.info(s"Successfully computed overall outcome for $updatedOutcomeCount/$applicationCount applications") + yield () + + private def computeAndSaveOverallOutcome(applicationWithIndividuals: ApplicationWithIndividuals)(using RequestHeader): Future[Unit] = + RiskingOutcomeHelper.computeRiskingOutcome(applicationWithIndividuals) match + case None => + logger.error(s"BUG: Missing risking results for application ${applicationWithIndividuals.application.applicationReference} - this should not happen") + Future.unit + case Some(outcome) => + val applicationForRisking: ApplicationForRisking = applicationWithIndividuals + .application + .modify(_.overallStatus.riskingOutcome) + .setTo(Some(outcome)) + applicationForRiskingRepo + .upsert(applicationForRisking) + .map(_ => ()) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/services/EmailServiceForApprovedApplications.scala b/app/uk/gov/hmrc/agentregistrationrisking/services/EmailServiceForApprovedApplications.scala new file mode 100644 index 0000000..f0aeff8 --- /dev/null +++ b/app/uk/gov/hmrc/agentregistrationrisking/services/EmailServiceForApprovedApplications.scala @@ -0,0 +1,83 @@ +/* + * Copyright 2026 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.agentregistrationrisking.services + +import com.softwaremill.quicklens.modify +import play.api.mvc.RequestHeader +import uk.gov.hmrc.agentregistration.shared.EmailAddress +import uk.gov.hmrc.agentregistrationrisking.connectors.EmailConnector +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.EmailTemplateId +import uk.gov.hmrc.agentregistrationrisking.model.SendEmailRequest +import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.util.ProcessInSequence +import uk.gov.hmrc.agentregistrationrisking.util.RequestAwareLogging + +import javax.inject.Inject +import javax.inject.Singleton +import scala.concurrent.ExecutionContext +import scala.concurrent.Future + +@Singleton +class EmailServiceForApprovedApplications @Inject() ( + emailConnector: EmailConnector, + applicationForRiskingRepo: ApplicationForRiskingRepo, + individualForRiskingRepo: IndividualForRiskingRepo +)(using ExecutionContext) +extends RequestAwareLogging: + + private val emailTemplateId: EmailTemplateId = EmailTemplateId.RegistrationSuccess + + def processEmails()(using RequestHeader): Future[Unit] = + for + applications: Seq[ApplicationForRisking] <- applicationForRiskingRepo.findSubscribedReadyForSuccessEmail() + applicationCount: Int = applications.size + _ = logger.info(s"Found $applicationCount subscribed applications ready to be sent an email: $emailTemplateId") + emailsSentSuccessCount <- + ProcessInSequence + .processAllInSequence(applications)(process): + case (ex, application) => logger.error(s"Failed to send email for ${application.applicationReference}: $emailTemplateId", ex) + _ = logger.info(s"Sent $emailsSentSuccessCount/$applicationCount $emailTemplateId emails") + yield () + + private def process(application: ApplicationForRisking)(using RequestHeader): Future[Unit] = + for + sendEmailRequest <- Future.successful(makeSendEmailRequest(application)) + _ = logger.info(s"Sending ${sendEmailRequest.templateId} email for ${application.applicationReference}") + _ <- emailConnector.sendEmail(sendEmailRequest) + _ <- applicationForRiskingRepo.upsert( + application + .copy(isEmailSent = true) + .modify(_.overallStatus.emailsProcessed) + .setTo(true) + ) + _ = logger.info(s"Sent ${sendEmailRequest.templateId} email for ${application.applicationReference}") + yield () + + private def makeSendEmailRequest(application: ApplicationForRisking): SendEmailRequest = + val agentApplication = application.agentApplication + val agentDetails = agentApplication.getAgentDetails + SendEmailRequest( + to = Seq(EmailAddress(agentDetails.getAgentEmailAddress.getEmailAddress)), + templateId = emailTemplateId, + parameters = Map( + "agentName" -> agentApplication.getApplicantContactDetails.applicantName.value, + "applicationRef" -> agentApplication.applicationReference.value, + "businessName" -> agentDetails.businessName.getAgentBusinessName + ) + ) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/services/EmailServiceForFailedNonFixable.scala b/app/uk/gov/hmrc/agentregistrationrisking/services/EmailServiceForFailedNonFixable.scala new file mode 100644 index 0000000..aacb8ba --- /dev/null +++ b/app/uk/gov/hmrc/agentregistrationrisking/services/EmailServiceForFailedNonFixable.scala @@ -0,0 +1,133 @@ +/* + * Copyright 2026 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.agentregistrationrisking.services + +import com.softwaremill.quicklens.modify +import play.api.mvc.RequestHeader +import uk.gov.hmrc.agentregistration.shared.BusinessType +import uk.gov.hmrc.agentregistration.shared.EmailAddress +import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.=== +import uk.gov.hmrc.agentregistrationrisking.connectors.EmailConnector +import uk.gov.hmrc.agentregistrationrisking.model.* +import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.util.ProcessInSequence +import uk.gov.hmrc.agentregistrationrisking.util.RequestAwareLogging + +import javax.inject.Inject +import javax.inject.Singleton +import scala.concurrent.ExecutionContext +import scala.concurrent.Future + +@Singleton +class EmailServiceForFailedNonFixable @Inject() ( + emailConnector: EmailConnector, + applicationForRiskingRepo: ApplicationForRiskingRepo, + individualForRiskingRepo: IndividualForRiskingRepo +)(using ExecutionContext) +extends RequestAwareLogging: + + private val emailTemplateId: EmailTemplateId = EmailTemplateId.ApplicationNonFixableFailure + + def processEmails()(using requestHeader: RequestHeader): Future[Unit] = + for + applicationsWithIndividuals: Seq[ApplicationWithIndividuals] <- applicationForRiskingRepo.findRequiringEmailProcessingForFailedNonFixable() + applicationsCount: Int = applicationsWithIndividuals.size + _ = logger.info(s"Found $applicationsCount FailedNonFixable applications with individuals ready to process emails") + successfullyProcessedCount <- + ProcessInSequence.processAllInSequence(applicationsWithIndividuals)(process): + case (ex, applicationWithIndividuals) => + logger.error( + s"Failed to process emails for FailedNonFixable application: ${applicationWithIndividuals.application.applicationReference}", + ex + ) + _ = logger.info(s"Processed emails for $successfullyProcessedCount/$applicationsCount FailedNonFixable applications") + yield () + + private def process(applicationWithIndividuals: ApplicationWithIndividuals)(using RequestHeader): Future[Unit] = + val application: ApplicationForRisking = applicationWithIndividuals.application + val individuals: Seq[IndividualForRisking] = + application.agentApplication.businessType match + case BusinessType.SoleTrader => applicationWithIndividuals.individuals.filterNot(isIndividualTheApplicant(_, application)) + case _ => applicationWithIndividuals.individuals + + for + updatedApplication <- process(application) + _ <- + ProcessInSequence + .processInSequence(individuals.filter(_.isEmailSent === false))(process) + _ <- applicationForRiskingRepo + .upsert(updatedApplication.modify(_.overallStatus.emailsProcessed).setTo(true)) + yield () + + private def process(application: ApplicationForRisking)(using RequestHeader): Future[ApplicationForRisking] = + if application.isEmailSent + then Future.successful(application) + else + for + sendEmailRequest <- Future.successful(makeSendEmailRequest(application)) + _ = logger.info(s"Sending ${sendEmailRequest.templateId} email for ${application.applicationReference}") + _ <- emailConnector.sendEmail(sendEmailRequest) + updatedApplication = application.copy(isEmailSent = true) + _ <- applicationForRiskingRepo.upsert(updatedApplication) + _ = logger.info(s"Sent ${sendEmailRequest.templateId} email for ${application.applicationReference}") + yield updatedApplication + + private def process(individual: IndividualForRisking)(using RequestHeader): Future[Unit] = + for + sendEmailRequest <- Future.successful(makeSendEmailRequest(individual)) + _ = logger.info(s"Sending ${sendEmailRequest.templateId} email for ${individual.applicationReference} ${individual.personReference}") + _ <- emailConnector.sendEmail(sendEmailRequest) + _ <- individualForRiskingRepo.upsert(individual.copy(isEmailSent = true)) + _ = logger.info(s"Sent ${sendEmailRequest.templateId} email for ${individual.applicationReference} ${individual.personReference}") + yield () + + private def isIndividualTheApplicant( + individual: IndividualForRisking, + application: ApplicationForRisking + ): Boolean = + val maybeTheSame: Option[Boolean] = + for + applicantContactDetails <- application.agentApplication.applicantContactDetails + applicantEmailAddress <- applicantContactDetails.applicantEmailAddress.map(_.emailAddress) + individualEmail <- individual.individualProvidedDetails.emailAddress.map(_.emailAddress) + yield (applicantEmailAddress.value === individualEmail.value) + + maybeTheSame.getOrElse(false) + + private def makeSendEmailRequest(application: ApplicationForRisking): SendEmailRequest = + val agentApplication = application.agentApplication + SendEmailRequest( + to = Seq(EmailAddress(agentApplication.getAgentDetails.getAgentEmailAddress.getEmailAddress)), + templateId = emailTemplateId, + parameters = Map( + "agentName" -> agentApplication.getApplicantContactDetails.applicantName.value, + "applicationRef" -> agentApplication.applicationReference.value + ) + ) + + private def makeSendEmailRequest( + individual: IndividualForRisking + ): SendEmailRequest = + val individualDetails = individual.individualProvidedDetails + SendEmailRequest( + to = Seq(individualDetails.getEmailAddress.emailAddress), + templateId = EmailTemplateId.IndividualNonFixableFailure, + parameters = Map( + "individualName" -> individualDetails.individualName.value + ) + ) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/services/RiskingOutcomeHelper.scala b/app/uk/gov/hmrc/agentregistrationrisking/services/RiskingOutcomeHelper.scala index ac0299c..9d77985 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/services/RiskingOutcomeHelper.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/services/RiskingOutcomeHelper.scala @@ -20,6 +20,7 @@ import uk.gov.hmrc.agentregistration.shared.risking.* import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.model.RiskingOutcome import java.time.LocalDate @@ -69,7 +70,7 @@ object RiskingOutcomeHelper: o1: RiskingOutcome, o2: RiskingOutcome ): RiskingOutcome = - import RiskingOutcome.* + import uk.gov.hmrc.agentregistrationrisking.model.RiskingOutcome.* // Hint: a spoiled apple makes a spoiled basket (o1, o2) match // format: off diff --git a/app/uk/gov/hmrc/agentregistrationrisking/services/SubscriptionService.scala b/app/uk/gov/hmrc/agentregistrationrisking/services/SubscriptionService.scala index e888991..1796348 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/services/SubscriptionService.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/services/SubscriptionService.scala @@ -18,9 +18,6 @@ package uk.gov.hmrc.agentregistrationrisking.services import play.api.mvc.RequestHeader import uk.gov.hmrc.agentregistration.shared.AgentApplication -import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.=== -import uk.gov.hmrc.agentregistration.shared.risking.RiskingOutcome -import uk.gov.hmrc.agentregistration.shared.risking.RiskingOutcome.* import uk.gov.hmrc.agentregistration.shared.util.Errors.getOrThrowExpectedDataMissing import uk.gov.hmrc.agentregistrationrisking.connectors.EnrolmentStoreProxyConnector.EnrolmentRequest import uk.gov.hmrc.agentregistrationrisking.connectors.EnrolmentStoreProxyConnector.KnownFact @@ -28,13 +25,8 @@ import uk.gov.hmrc.agentregistrationrisking.connectors.EnrolmentStoreProxyConnec import uk.gov.hmrc.agentregistrationrisking.connectors.EnrolmentStoreProxyConnector import uk.gov.hmrc.agentregistrationrisking.connectors.HipConnector import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking -import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals -import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking -import uk.gov.hmrc.agentregistrationrisking.model.RiskingResult -import uk.gov.hmrc.agentregistrationrisking.model.hip.Arn import uk.gov.hmrc.agentregistrationrisking.model.hip.SubscribeAgentRequest import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo -import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo import uk.gov.hmrc.agentregistrationrisking.util.ProcessInSequence import uk.gov.hmrc.agentregistrationrisking.util.RequestAwareLogging @@ -44,33 +36,25 @@ import javax.inject.Inject import javax.inject.Singleton import scala.concurrent.ExecutionContext import scala.concurrent.Future -import scala.util.chaining.scalaUtilChainingOps @Singleton class SubscriptionService @Inject() ( applicationForRiskingRepo: ApplicationForRiskingRepo, - individualForRiskingRepo: IndividualForRiskingRepo, hipConnector: HipConnector, enrolmentStoreProxyConnector: EnrolmentStoreProxyConnector, clock: Clock )(using ExecutionContext) extends RequestAwareLogging: - def subscribeApprovedApplications()(using RequestHeader): Future[Unit] = + def processSubscriptions()(using RequestHeader): Future[Unit] = logger.info("Subscribing approved applications...") for - applications: Seq[ApplicationForRisking] <- findApprovedReadyToSubscribe() + applications: Seq[ApplicationForRisking] <- applicationForRiskingRepo.findReadyToBeSubscribed() applicationCount: Int = applications.size _ = logger.info(s"Found $applicationCount applications ready to subscribe") - subscriptionSuccessCount <- ProcessInSequence - .processInSequence(applications): application => - subscribeApplication(application) - .map(_ => true) - .recover: - case ex: Throwable => - logger.error(s"Failed to subscribe agent: ${application.agentApplication.applicationReference.value}: ${ex.getMessage}") - false - .map(_.count(identity)) + subscriptionSuccessCount <- + ProcessInSequence.processAllInSequence(applications)(subscribeApplication): + case (ex, application) => logger.error(s"Failed to subscribe agent: ${application.agentApplication.applicationReference.value}", ex) _ = logger.info(s"Subscribed $subscriptionSuccessCount/$applicationCount applications") yield () @@ -82,19 +66,6 @@ extends RequestAwareLogging: _ = logger.info(s"Application subscribed: ${application.applicationReference}") yield () - private def findApprovedReadyToSubscribe()(using RequestHeader): Future[Seq[ApplicationForRisking]] = - for - applications: Seq[ApplicationForRisking] <- applicationForRiskingRepo.findNotSubscribedWithResults() - individuals: Seq[IndividualForRisking] <- individualForRiskingRepo.findByApplicationReferences(applications.map(_.applicationReference)) - applicationsWithIndividuals: Seq[ApplicationWithIndividuals] = ApplicationWithIndividuals.merge(applications, individuals) - approvedApplicationsWithIndividuals: Seq[ApplicationWithIndividuals] = applicationsWithIndividuals - .filter: applicationWithIndividuals => - RiskingOutcomeHelper - .computeRiskingOutcome(applicationWithIndividuals) - .exists(_ === RiskingOutcome.Approved) - approvedApplications: Seq[ApplicationForRisking] = approvedApplicationsWithIndividuals.map(_.application) - yield approvedApplications - private def subscribeAgent(agentApplication: AgentApplication)(using RequestHeader): Future[Unit] = val agentDetails = agentApplication.getAgentDetails val amlsDetails = agentApplication.getAmlsDetails diff --git a/app/uk/gov/hmrc/agentregistrationrisking/testOnly/services/TestOnlyRiskingResultsService.scala b/app/uk/gov/hmrc/agentregistrationrisking/testOnly/services/TestOnlyRiskingResultsService.scala new file mode 100644 index 0000000..c5dac7d --- /dev/null +++ b/app/uk/gov/hmrc/agentregistrationrisking/testOnly/services/TestOnlyRiskingResultsService.scala @@ -0,0 +1,113 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testOnly.services + +import play.api.mvc.RequestHeader +import uk.gov.hmrc.agentregistrationrisking.connectors.RiskingResultsFileConnector +import uk.gov.hmrc.agentregistrationrisking.connectors.SdesProxyConnector +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.model.EntityRiskingResult +import uk.gov.hmrc.agentregistrationrisking.model.IndividualRiskingResult +import uk.gov.hmrc.agentregistrationrisking.model.RiskingResult +import uk.gov.hmrc.agentregistrationrisking.model.RiskingResultParser +import uk.gov.hmrc.agentregistrationrisking.model.sdes.AvailableFile +import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.services.ObjectStoreService +import uk.gov.hmrc.agentregistrationrisking.util.ProcessInSequence +import uk.gov.hmrc.agentregistrationrisking.util.RequestAwareLogging +import uk.gov.hmrc.objectstore.client.ObjectListing + +import java.time.Clock +import java.time.Instant +import javax.inject.Inject +import javax.inject.Singleton +import scala.concurrent.ExecutionContext +import scala.concurrent.Future + +@Singleton +class TestOnlyRiskingResultsService @Inject() ( + sdesProxyConnector: SdesProxyConnector, + applicationForRiskingRepo: ApplicationForRiskingRepo, + individualForRiskingRepo: IndividualForRiskingRepo, + objectStoreService: ObjectStoreService, + riskingResultsFileConnector: RiskingResultsFileConnector, + clock: Clock +)(using ExecutionContext) +extends RequestAwareLogging: + + def processResultsFilesSkipUpload()(using request: RequestHeader): Future[Unit] = + logger.info(s"Processing RiskingResultsFile(s) (test-only skip-upload mode) ...") + for + unprocessedAvailableFiles <- getUnprocessedAvailableFiles() + _ = logger.info(s"Found ${unprocessedAvailableFiles.size} RiskingResultsFile(s) to process") + numberOfProcessedFiles <- ProcessInSequence.processInSequence(unprocessedAvailableFiles)(processResultsFileSkipUpload).map(_.size) + _ = logger.info(s"Processing RiskingResultsFile(s) COMPLETE: $numberOfProcessedFiles") + yield () + + private def processResultsFileSkipUpload(availableFile: AvailableFile)(using request: RequestHeader): Future[Unit] = + logger.info(s"Processing RiskingResultsFile (skip-upload): ${availableFile.filename}, size:${availableFile.fileSize}...") + for + riskingResults <- riskingResultsFileConnector + .getRiskingResultRecords(availableFile = availableFile) + .map(_.map(RiskingResultParser.parseRiskingResult)) + _ = logger.info(s"Downloaded and parsed risking results file: ${availableFile.filename} (${riskingResults.size} records)") + numberOfUpdates <- ProcessInSequence.processInSequence(riskingResults)(processRiskingResult).map(_.size) + _ = logger.info(s"Updated matching $numberOfUpdates applications and individuals with retrieved risking results") + yield () + + private def getUnprocessedAvailableFiles()(using request: RequestHeader): Future[Seq[AvailableFile]] = + for + availableFiles <- sdesProxyConnector.listAvailableFiles + filesAlreadyProcessed: ObjectListing <- objectStoreService.listObjects + fileNamesAlreadyProcessed = filesAlreadyProcessed.objectSummaries.map(_.location.fileName).toSet + unprocessedAvailableFiles = availableFiles.filterNot(f => fileNamesAlreadyProcessed.contains(f.filename)) + yield unprocessedAvailableFiles + + private def processRiskingResult(riskingResult: RiskingResult)(using request: RequestHeader): Future[Unit] = + riskingResult match + case entity: RiskingResult.ForEntity => updateEntity(entity) + case individual: RiskingResult.ForIndividual => updateIndividual(individual) + + private def updateEntity(riskingResult: RiskingResult.ForEntity)(using request: RequestHeader): Future[Unit] = applicationForRiskingRepo + .findById(riskingResult.applicationReference) + .flatMap: + case None => + logger.error(s"Missing application for: ${riskingResult.applicationReference}") + Future.unit + case Some(application) => + val now = Instant.now(clock) + val updatedApplication: ApplicationForRisking = application.copy( + entityRiskingResult = Some(EntityRiskingResult(failures = riskingResult.failures, receivedAt = now)), + lastUpdatedAt = now + ) + applicationForRiskingRepo.upsert(updatedApplication).map(_ => ()) + + private def updateIndividual(riskingResult: RiskingResult.ForIndividual)(using request: RequestHeader): Future[Unit] = individualForRiskingRepo + .findById(riskingResult.personReference) + .flatMap: + case None => + logger.error(s"Missing individual for: ${riskingResult.personReference}") + Future.unit + case Some(individual) => + val now = Instant.now(clock) + val updatedIndividual: IndividualForRisking = individual.copy( + individualRiskingResult = Some(IndividualRiskingResult(failures = riskingResult.failures, receivedAt = now)), + lastUpdatedAt = now + ) + individualForRiskingRepo.upsert(updatedIndividual).map(_ => ()) diff --git a/app/uk/gov/hmrc/agentregistrationrisking/util/ProcessInSequence.scala b/app/uk/gov/hmrc/agentregistrationrisking/util/ProcessInSequence.scala index ffbdf90..56e380f 100644 --- a/app/uk/gov/hmrc/agentregistrationrisking/util/ProcessInSequence.scala +++ b/app/uk/gov/hmrc/agentregistrationrisking/util/ProcessInSequence.scala @@ -34,3 +34,36 @@ object ProcessInSequence: ) => acc.flatMap(list => f(item).map(_ :: list)) .map(_.reverse) + + /** Processes a sequence of items sequentially, applying an asynchronous function to each item in order, without stopping on failures. + * + * Unlike `processInSequence`, this method does not stop processing when an item fails. Instead, it continues processing all items in the sequence. When an + * item's processing fails, the `onFailure` hook is called with the exception and the failed item, allowing for custom error handling (e.g., logging). + * + * This is particularly useful when you need to process a batch of items and want to ensure all items are attempted, even if some fail, while still being + * notified of failures. + * + * @param items + * The sequence of items to process. + * @param f + * The asynchronous function to apply to each item. Failures are caught and handled via the onFailure hook. + * @param onFailure + * A callback function invoked when processing of an item fails. It receives the exception and the item that failed. + * @return + * A Future containing the count of successfully processed items. + */ + def processAllInSequence[Item, Result]( + items: Seq[Item] + )( + f: Item => Future[Result] + )( + onFailure: ( + Throwable, + Item + ) => Unit + )(using + ExecutionContext + ): Future[Int] = processInSequence(items)(i => + f(i).map(_ => true).recover: + case ex => onFailure(ex, i); false + ).map(_.count(identity)) diff --git a/conf/application.conf b/conf/application.conf index e59078c..aa57578 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -64,6 +64,10 @@ microservice { host = localhost port = 8500 } + email { + host = localhost + port = 8300 + } enrolment-store-proxy { host = localhost port = 7775 diff --git a/test/uk/gov/hmrc/agentregistrationrisking/connectors/EmailConnectorSpec.scala b/test/uk/gov/hmrc/agentregistrationrisking/connectors/EmailConnectorSpec.scala new file mode 100644 index 0000000..b0c1ee3 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/connectors/EmailConnectorSpec.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2026 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.agentregistrationrisking.connectors + +import play.api.mvc.RequestHeader +import play.api.test.FakeRequest +import uk.gov.hmrc.agentregistrationrisking.testsupport.ISpec +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdAll.tdAll.* +import uk.gov.hmrc.agentregistrationrisking.testsupport.wiremock.stubs.EmailStubs + +class EmailConnectorSpec +extends ISpec: + + val connector: EmailConnector = app.injector.instanceOf[EmailConnector] + + "completes successfully when the email service responds with 202 Accepted" in: + given RequestHeader = FakeRequest() + EmailStubs.stubSendEmail(sendEmailRequest) + connector.sendEmail(sendEmailRequest).futureValue shouldBe (()) + EmailStubs.verifySendEmail() + + "fails when the email service responds with a non-2xx status" in: + given RequestHeader = FakeRequest() + EmailStubs.stubSendEmailFailure(sendEmailRequest) + val exception = connector.sendEmail(sendEmailRequest).failed.futureValue + exception shouldBe a[Throwable] + EmailStubs.verifySendEmail() diff --git a/test/uk/gov/hmrc/agentregistrationrisking/controllers/RiskingProgressControllerForApplicantSpec.scala b/test/uk/gov/hmrc/agentregistrationrisking/controllers/RiskingProgressControllerForApplicantSpec.scala index 9d96c53..c593e1a 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/controllers/RiskingProgressControllerForApplicantSpec.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/controllers/RiskingProgressControllerForApplicantSpec.scala @@ -16,15 +16,16 @@ package uk.gov.hmrc.agentregistrationrisking.controllers +import org.mongodb.scala.SingleObservableFuture import play.api.mvc.Call import uk.gov.hmrc.agentregistration.shared.ApplicationReference +import uk.gov.hmrc.agentregistration.shared.PersonReference import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking -import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo import uk.gov.hmrc.agentregistrationrisking.testsupport.ControllerSpec -import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals import uk.gov.hmrc.agentregistrationrisking.testsupport.wiremock.stubs.AuthStubs import java.net.URL @@ -32,36 +33,28 @@ import java.net.URL class RiskingProgressControllerForApplicantSpec extends ControllerSpec: - val tdRisking: TdRisking = tdAll.tdRisking - val applicationReference: ApplicationReference = tdRisking.agentApplication.applicationReference - val applicationForRiskingRepo: ApplicationForRiskingRepo = app.injector.instanceOf[ApplicationForRiskingRepo] val individualForRiskingRepo: IndividualForRiskingRepo = app.injector.instanceOf[IndividualForRiskingRepo] - val path: String = s"/agent-registration-risking/risking-progress/for-applicant" - def riskingProgressForApplicantUrl(agentApplicationReference: ApplicationReference): URL = url"${baseUrl + path}/${agentApplicationReference.value}" + val pathForApplicant: String = s"/agent-registration-risking/risking-progress/for-applicant" + val pathForIndividual: String = s"/agent-registration-risking/risking-progress/for-individual" - def primeDbWithBackgroundData(): Unit = - applicationForRiskingRepo.collection.drop() - individualForRiskingRepo.collection.drop() - applicationForRiskingRepo.upsert(tdAll.tdRisking2.tdApplicationForRisking.readyForSubmission).futureValue - individualForRiskingRepo.upsert(tdAll.tdRisking2.tdIndividualsForRisking.tdIndividualForRisking1.readyForSubmission).futureValue - individualForRiskingRepo.upsert(tdAll.tdRisking2.tdIndividualsForRisking.tdIndividualForRisking2.readyForSubmission).futureValue - applicationForRiskingRepo.upsert(tdAll.tdRisking3.tdApplicationForRisking.submittedForRisking).futureValue - individualForRiskingRepo.upsert(tdAll.tdRisking3.tdIndividualsForRisking.tdIndividualForRisking1.submittedForRisking).futureValue - individualForRiskingRepo.upsert(tdAll.tdRisking3.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking).futureValue - () + def riskingProgressForApplicantUrl(agentApplicationReference: ApplicationReference): URL = + url"${baseUrl + pathForApplicant}/${agentApplicationReference.value}" + def riskingProgressForIndividualUrl(personRerence: PersonReference): URL = url"${baseUrl + pathForApplicant}/${personRerence.value}" "routes should have correct paths and methods" in: + val applicationReference: ApplicationReference = ApplicationReference("APPREF_123") val call: Call = routes.RiskingProgressController.getRiskingProgressForApplicant(applicationReference) call shouldBe Call( method = "GET", - url = s"$path/${applicationReference.value}" + url = s"$pathForApplicant/${applicationReference.value}" ) "returns NO_CONTENT if there is no underlying records" in: given Request[?] = tdAll.backendRequest AuthStubs.stubAuthorise() + val applicationReference: ApplicationReference = ApplicationReference("APPREF_123") applicationForRiskingRepo.findById(applicationReference).futureValue shouldBe None withClue " no prior records in mongo for this application" individualForRiskingRepo.findByApplicationReference( @@ -78,101 +71,33 @@ extends ControllerSpec: response.body shouldBe "" AuthStubs.verifyAuthorise() - final case class TestCase( - description: String, - application: ApplicationForRisking, - individuals: Seq[IndividualForRisking], - expectedRiskingProgress: RiskingProgress - ) - - val testCases: Seq[TestCase] = List( - TestCase( - description = "application is readyForSubmission", - application = tdRisking.tdApplicationForRisking.readyForSubmission, - individuals = Seq( - tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.readyForSubmission, - tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.readyForSubmission - ), - expectedRiskingProgress = RiskingProgress.ReadyForSubmission - ), - TestCase( - description = "application is submittedForRisking", - application = tdRisking.tdApplicationForRisking.submittedForRisking, - individuals = Seq( - tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.submittedForRisking, - tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking - ), - expectedRiskingProgress = RiskingProgress.SubmittedForRisking - ), - TestCase( - description = "partial risking results: application:approved, individual1:submittedForRisking, individual2:submittedForRisking", - application = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved, - individuals = Seq( - tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.submittedForRisking, - tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking - ), - expectedRiskingProgress = RiskingProgress.SubmittedForRisking - ), - TestCase( - description = "partial risking results: application:approved, individual1:approved, individual2: submittedForRisking", - application = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved, - individuals = Seq( - tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved, - tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking - ), - expectedRiskingProgress = RiskingProgress.SubmittedForRisking - ), - TestCase( - description = "all approved: application:approved, individual1:approved, individual2:approved", - application = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved, - individuals = Seq( - tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved, - tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved - ), - expectedRiskingProgress = RiskingProgress.Approved - ) - ) - - primeDbWithBackgroundData() - - testCases.foreach: tc => - tc.description in: - applicationForRiskingRepo.upsert(tc.application).futureValue - tc.individuals.foreach(individualForRiskingRepo.upsert(_).futureValue) - given Request[?] = tdAll.backendRequest - AuthStubs.stubAuthorise() - - val applicationReference: ApplicationReference = tc.application.applicationReference - val riskingStatusForApplicantResponse: HttpResponse = - httpClient - .get(riskingProgressForApplicantUrl(applicationReference)) - .execute[HttpResponse] - .futureValue - - riskingStatusForApplicantResponse.status shouldBe Status.OK - val riskingStatusForApplicant: RiskingProgress = riskingStatusForApplicantResponse.json.as[RiskingProgress] - riskingStatusForApplicant shouldBe tc.expectedRiskingProgress - AuthStubs.verifyAuthorise() - -// "find application with individuals returns Ok with individuals in response" in: -// given Request[?] = tdAll.backendRequest -// AuthStubs.stubAuthorise() -// -// val application = tdAll.llpApplicationForRisking.copy(_id = ApplicationForRiskingId("test-app-with-individuals")) -// applicationForRiskingRepo.upsert(application).futureValue -// -// val individual = tdAll.readyForSubmissionIndividual(application._id) -// individualForRiskingRepo.upsert(individual).futureValue -// -// val response = -// httpClient -// .get(url"$baseUrl/agent-registration-risking/application/${application.agentApplication.applicationReference.value}") -// .execute[HttpResponse] -// .futureValue -// response.status shouldBe Status.OK -// val parsedResponse = response.json.as[RiskingProgress] -// parsedResponse.applicationReference shouldBe application.agentApplication.applicationReference -// parsedResponse.individuals.size shouldBe 1 -// parsedResponse.individuals.headOption.value.personReference shouldBe tdAll.personReference -// parsedResponse.individuals.headOption.value.failures shouldBe None -// AuthStubs.verifyAuthorise() + tdAll + .tdRiskingInstancesInStates + .all + .foreach: (td: TdApplicationWithIndividuals) => + s"return correct riskingProgress - $td" in: + AuthStubs.stubAuthorise() + given Request[?] = tdAll.backendRequest + val applicationReference: ApplicationReference = td.application.applicationReference + val riskingStatusForApplicantResponse: HttpResponse = + httpClient + .get(riskingProgressForApplicantUrl(applicationReference)) + .execute[HttpResponse] + .futureValue + riskingStatusForApplicantResponse.status shouldBe Status.OK + val riskingStatusForApplicant: RiskingProgress = riskingStatusForApplicantResponse.json.as[RiskingProgress] + riskingStatusForApplicant shouldBe td.riskingProgressForApplicant + AuthStubs.verifyAuthorise() + + override protected def beforeAll(): Unit = + super.beforeAll() + + def primeDbWithBackgroundData(): Unit = + applicationForRiskingRepo.collection.drop().toFuture.futureValue + individualForRiskingRepo.collection.drop().toFuture.futureValue + tdAll.tdRiskingInstancesInStates.all.foreach: td => + applicationForRiskingRepo.upsert(td.application).futureValue + individualForRiskingRepo.upsert(td.individual1).futureValue + individualForRiskingRepo.upsert(td.individual2).futureValue + + primeDbWithBackgroundData() diff --git a/test/uk/gov/hmrc/agentregistrationrisking/controllers/SubmitForRiskingControllerSpec.scala b/test/uk/gov/hmrc/agentregistrationrisking/controllers/SubmitForRiskingControllerSpec.scala index 8a739dc..5732378 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/controllers/SubmitForRiskingControllerSpec.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/controllers/SubmitForRiskingControllerSpec.scala @@ -24,6 +24,7 @@ import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo import uk.gov.hmrc.agentregistrationrisking.testsupport.ControllerSpec +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRiskingInstancesInStates import uk.gov.hmrc.agentregistrationrisking.testsupport.wiremock.stubs.AuthStubs class SubmitForRiskingControllerSpec @@ -42,15 +43,18 @@ extends ControllerSpec: "submit application and individuals for risking for the first time" in: // GIVEN + dropDatabase() given Request[?] = tdAll.backendRequest AuthStubs.stubAuthorise() - val submitRequest: SubmitForRiskingRequest = tdAll.tdRisking.submitForRiskingRequest + val td = tdAll.tdRiskingInstancesInStates.readyForSubmission + + val submitRequest: SubmitForRiskingRequest = td.tdRisking.submitForRiskingRequest val applicationReference: ApplicationReference = submitRequest.agentApplication.applicationReference - val applicationForRiskingSubmitted: ApplicationForRisking = tdAll.tdRisking.tdApplicationForRisking.readyForSubmission - val individualForRiskingSubmitted1: IndividualForRisking = tdAll.tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.readyForSubmission - val individualForRiskingSubmitted2: IndividualForRisking = tdAll.tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.readyForSubmission + val applicationForRiskingSubmitted: ApplicationForRisking = td.application + val individualForRiskingSubmitted1: IndividualForRisking = td.individual1 + val individualForRiskingSubmitted2: IndividualForRisking = td.individual2 applicationForRiskingRepo .findById(applicationReference) diff --git a/test/uk/gov/hmrc/agentregistrationrisking/controllers/smu/SmuViewerControllerSpec.scala b/test/uk/gov/hmrc/agentregistrationrisking/controllers/smu/SmuViewerControllerSpec.scala index 404b233..7701cb7 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/controllers/smu/SmuViewerControllerSpec.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/controllers/smu/SmuViewerControllerSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2026 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. + */ + ///* // * Copyright 2026 HM Revenue & Customs // * diff --git a/test/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepoSpec.scala b/test/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepoSpec.scala index 2c0be28..0f59e90 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepoSpec.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepoSpec.scala @@ -14,162 +14,101 @@ * limitations under the License. */ -///* -// * Copyright 2026 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.agentregistrationrisking.repository -// -//import uk.gov.hmrc.agentregistration.shared.risking.EntityFailure -//import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRiskingId -//import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName -//import uk.gov.hmrc.agentregistrationrisking.testsupport.ISpec -//import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdAll.tdAll.* -// -//class ApplicationForRiskingRepoSpec -//extends ISpec: -// -// val repo: ApplicationForRiskingRepo = app.injector.instanceOf[ApplicationForRiskingRepo] -// -// "findByApplicationReference" - { -// -// "returns application when found" in { -// val app = tdAll.llpApplicationForRisking.copy(_id = ApplicationForRiskingId("find-ref-1")) -// repo.upsert(app).futureValue -// -// val result = repo.findByApplicationReference(app.agentApplication.applicationReference).futureValue -// result.value._id shouldBe app._id -// } -// -// "returns None when not found" in { -// val result = repo.findByApplicationReference(uk.gov.hmrc.agentregistration.shared.ApplicationReference("NON_EXISTENT")).futureValue -// result shouldBe None -// } -// } -// -// "findReadyForSubmission" - { -// -// "returns applications without riskingFileId" in { -// val ready = tdAll.llpApplicationForRisking.copy(_id = ApplicationForRiskingId("ready-1")) -// val submitted = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("submitted-1"), -// riskingFileName = Some(RiskingFileName("file-1")) -// ) -// repo.upsert(ready).futureValue -// repo.upsert(submitted).futureValue -// -// val result = repo.findReadyForSubmission().futureValue -// result.size shouldBe 1 -// result.headOption.value._id shouldBe ready._id -// } -// } -// -// "findReadyForSubscription" - { -// -// "returns applications with empty failures and not subscribed" in { -// val ready = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("sub-ready-1"), -// failures = Some(List.empty) -// ) -// repo.upsert(ready).futureValue -// -// val result = repo.findReadyForSubscription().futureValue -// result.size shouldBe 1 -// result.headOption.value._id shouldBe ready._id -// } -// -// "does not return applications with non-empty failures" in { -// val failed = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("sub-failed-1"), -// failures = Some(List(EntityFailure._3._2)) -// ) -// repo.upsert(failed).futureValue -// -// val result = repo.findReadyForSubscription().futureValue -// result.size shouldBe 0 -// } -// -// "does not return applications without failures" in { -// val noFailures = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("sub-none-1"), -// failures = None -// ) -// repo.upsert(noFailures).futureValue -// -// val result = repo.findReadyForSubscription().futureValue -// result.size shouldBe 0 -// } -// -// "does not return already subscribed applications" in { -// val subscribed = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("sub-done-1"), -// failures = Some(List.empty), -// isSubscribed = true -// ) -// repo.upsert(subscribed).futureValue -// -// val result = repo.findReadyForSubscription().futureValue -// result.size shouldBe 0 -// } -// } -// -// "findNotSubscribedWithResults" - { -// -// "returns applications with failures present and not subscribed" in { -// val withResults = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("results-1"), -// failures = Some(List(EntityFailure._3._2)) -// ) -// repo.upsert(withResults).futureValue -// -// val result = repo.findNotSubscribedWithResults().futureValue -// result.size shouldBe 1 -// result.headOption.value._id shouldBe withResults._id -// } -// -// "returns applications with empty failures and not subscribed" in { -// val emptyFailures = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("results-empty-1"), -// failures = Some(List.empty) -// ) -// repo.upsert(emptyFailures).futureValue -// -// val result = repo.findNotSubscribedWithResults().futureValue -// result.size shouldBe 1 -// } -// -// "does not return applications without failures" in { -// val noFailures = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("results-none-1"), -// failures = None -// ) -// repo.upsert(noFailures).futureValue -// -// val result = repo.findNotSubscribedWithResults().futureValue -// result.size shouldBe 0 -// } -// -// "does not return already subscribed applications" in { -// val subscribed = tdAll.llpApplicationForRisking.copy( -// _id = ApplicationForRiskingId("results-sub-1"), -// failures = Some(List(EntityFailure._3._2)), -// isSubscribed = true -// ) -// repo.upsert(subscribed).futureValue -// -// val result = repo.findNotSubscribedWithResults().futureValue -// result.size shouldBe 0 -// } -// } +package uk.gov.hmrc.agentregistrationrisking.repository + +import org.mongodb.scala.SingleObservableFuture +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.ISpec +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRiskingInstancesInStates + +class ApplicationForRiskingRepoSpec +extends ISpec: + + "findReadyForSubmission should return all applications which don't have riskingFileId, which means they aren't submitted to minerva yet" in: + val applications: Seq[ApplicationForRisking] = + applicationForRiskingRepo + .findReadyForSubmission() + .futureValue + applications.toSet shouldBe Set( + TdRiskingInstancesInStates.readyForSubmission.application, + TdRiskingInstancesInStates.readyForSubmission2.application + ) + + "findReadyToBeSubscribed should return all applications which are approved but not subscribed yet" in: + + val applications: Seq[ApplicationForRisking] = + applicationForRiskingRepo + .findReadyToBeSubscribed() + .futureValue + + applications.size shouldBe 1 withClue applications.map(_.applicationReference.value).mkString(", ") + applications.toSet shouldBe Set(TdRiskingInstancesInStates.approvedAfterOutcome.application) + + "findReadyToSetRiskingOutcome" in: + + val applications: Seq[ApplicationWithIndividuals] = + applicationForRiskingRepo + .findReadyToSetRiskingOutcome() + .futureValue + + applications.toSet shouldBe Set( + TdRiskingInstancesInStates.approved.applicationWithIndividuals, + TdRiskingInstancesInStates.failedFixable.applicationWithIndividuals, + TdRiskingInstancesInStates.failedNonFixable.applicationWithIndividuals + ) + + "findRequiringEmailProcessingForFailedNonFixable" in: + + val applications: Seq[ApplicationWithIndividuals] = + applicationForRiskingRepo + .findRequiringEmailProcessingForFailedNonFixable() + .futureValue + + applications.toSet shouldBe Set( + TdRiskingInstancesInStates.failedNonFixableAfterOutcome.applicationWithIndividuals, + TdRiskingInstancesInStates.failedNonFixableAfter1EmailSent.applicationWithIndividuals, + TdRiskingInstancesInStates.failedNonFixableAfter2EmailsSent.applicationWithIndividuals, + TdRiskingInstancesInStates.failedNonFixableAfterAllEmailsSent.applicationWithIndividuals + ) withClue applications.toSet.map(_.application.applicationReference.value).mkString(",\n ") + + "findApplicationsAwaitingOverallOutcome" in: + + val applications: Seq[ApplicationWithIndividuals] = + applicationForRiskingRepo + .findApplicationsAwaitingOverallOutcome() + .futureValue + + applications.toSet shouldBe Set( + TdRiskingInstancesInStates.approved.applicationWithIndividuals, + TdRiskingInstancesInStates.failedFixable.applicationWithIndividuals, + TdRiskingInstancesInStates.failedNonFixable.applicationWithIndividuals + ) withClue applications.toSet.map(_.application.applicationReference.value).mkString(",\n ") + + "findSubscribedReadyForSuccessEmail" in: + + val applications: Seq[ApplicationForRisking] = + applicationForRiskingRepo + .findSubscribedReadyForSuccessEmail() + .futureValue + + applications.toSet shouldBe Set(TdRiskingInstancesInStates.approvedAfterSubscribed.application) + + private val applicationForRiskingRepo: ApplicationForRiskingRepo = app.injector.instanceOf[ApplicationForRiskingRepo] + private val individualForRiskingRepo: IndividualForRiskingRepo = app.injector.instanceOf[IndividualForRiskingRepo] + + override protected def beforeAll(): Unit = + super.beforeAll() + primeDb() + () + + private def primeDb(): Unit = + applicationForRiskingRepo.collection.drop().toFuture.futureValue + individualForRiskingRepo.collection.drop().toFuture.futureValue + tdAll + .tdRiskingInstancesInStates + .all + .foreach: td => + applicationForRiskingRepo.upsert(td.application).futureValue + individualForRiskingRepo.upsert(td.individual1).futureValue + individualForRiskingRepo.upsert(td.individual2).futureValue diff --git a/test/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepoUpdateRiskingFileNameSpec.scala b/test/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepoUpdateRiskingFileNameSpec.scala new file mode 100644 index 0000000..8134607 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/repository/ApplicationForRiskingRepoUpdateRiskingFileNameSpec.scala @@ -0,0 +1,100 @@ +/* + * Copyright 2026 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.agentregistrationrisking.repository + +import org.mongodb.scala.SingleObservableFuture +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName +import uk.gov.hmrc.agentregistrationrisking.testsupport.ISpec +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRiskingInstancesInStates + +class ApplicationForRiskingRepoUpdateRiskingFileNameSpec +extends ISpec: + + "updateRiskingFileId for one application" in: + + val application: ApplicationForRisking = tdAll.tdRiskingInstancesInStates.readyForSubmission.application + application.riskingFileName shouldBe None + + val riskingFileName: RiskingFileName = RiskingFileName("risking-file-name-1234") + + applicationForRiskingRepo.findById(application.applicationReference).futureValue shouldBe Some(application) + applicationForRiskingRepo.findByRiskingFileName(riskingFileName).futureValue shouldBe Seq.empty + + applicationForRiskingRepo + .updateRiskingFileName( + applicationReferences = Seq(application.applicationReference), + riskingFileName = riskingFileName + ) + .futureValue + + val applicationForRiskingWithUpdatedRiskingFileName: ApplicationForRisking = application.copy(riskingFileName = Some(riskingFileName)) + + applicationForRiskingRepo.findByRiskingFileName(riskingFileName).futureValue shouldBe Seq(applicationForRiskingWithUpdatedRiskingFileName) + applicationForRiskingRepo.findById(application.applicationReference).futureValue shouldBe Some(applicationForRiskingWithUpdatedRiskingFileName) + + "updateRiskingFileId for many applications" in: + + val application1: ApplicationForRisking = tdAll.tdRiskingInstancesInStates.readyForSubmission.application + application1.riskingFileName shouldBe None + val application2: ApplicationForRisking = tdAll.tdRiskingInstancesInStates.readyForSubmission2.application + application2.riskingFileName shouldBe None + + val riskingFileName: RiskingFileName = RiskingFileName("risking-file-name-1234") + + applicationForRiskingRepo.findById(application1.applicationReference).futureValue shouldBe Some(application1) + applicationForRiskingRepo.findById(application2.applicationReference).futureValue shouldBe Some(application2) + applicationForRiskingRepo.findByRiskingFileName(riskingFileName).futureValue shouldBe Seq.empty + + applicationForRiskingRepo + .updateRiskingFileName( + applicationReferences = Seq( + application1.applicationReference, + application2.applicationReference + ), + riskingFileName = riskingFileName + ) + .futureValue + + val application1WithUpdatedRiskingFileName: ApplicationForRisking = application1.copy(riskingFileName = Some(riskingFileName)) + val application2WithUpdatedRiskingFileName: ApplicationForRisking = application2.copy(riskingFileName = Some(riskingFileName)) + + applicationForRiskingRepo.findByRiskingFileName(riskingFileName).futureValue shouldBe Seq( + application1WithUpdatedRiskingFileName, + application2WithUpdatedRiskingFileName + ) + applicationForRiskingRepo.findById(application1.applicationReference).futureValue shouldBe Some(application1WithUpdatedRiskingFileName) + applicationForRiskingRepo.findById(application2.applicationReference).futureValue shouldBe Some(application2WithUpdatedRiskingFileName) + + private val applicationForRiskingRepo: ApplicationForRiskingRepo = app.injector.instanceOf[ApplicationForRiskingRepo] + private val individualForRiskingRepo: IndividualForRiskingRepo = app.injector.instanceOf[IndividualForRiskingRepo] + + override def beforeEach(): Unit = + super.beforeEach() + primeDb() + + private def primeDb(): Unit = + applicationForRiskingRepo.collection.drop().toFuture.futureValue + individualForRiskingRepo.collection.drop().toFuture.futureValue + tdAll + .tdRiskingInstancesInStates + .all + .foreach: td => + applicationForRiskingRepo.upsert(td.application).futureValue + individualForRiskingRepo.upsert(td.individual1).futureValue + individualForRiskingRepo.upsert(td.individual2).futureValue diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/ISpec.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/ISpec.scala index 5765322..503a041 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/ISpec.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/ISpec.scala @@ -17,6 +17,7 @@ package uk.gov.hmrc.agentregistrationrisking.testsupport import com.google.inject.AbstractModule +import org.scalatest.BeforeAndAfterAll import org.scalatest.BeforeAndAfterEach import org.scalatest.freespec.AnyFreeSpecLike import org.scalatestplus.play.guice.GuiceOneServerPerSuite @@ -31,6 +32,7 @@ import uk.gov.hmrc.agentregistrationrisking.model.CorrelationId import uk.gov.hmrc.agentregistrationrisking.model.CorrelationIdGenerator import uk.gov.hmrc.agentregistrationrisking.model.hip.HipAuthToken import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdAll +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdZoneId import uk.gov.hmrc.agentregistrationrisking.testsupport.wiremock.WireMockSupport import uk.gov.hmrc.http.HeaderCarrier import uk.gov.hmrc.mongo.test.MongoSupport @@ -43,6 +45,7 @@ import scala.concurrent.ExecutionContext trait ISpec extends AnyFreeSpecLike, BeforeAndAfterEach, + BeforeAndAfterAll, GuiceOneServerPerSuite, WireMockSupport, RichMatchers, @@ -56,8 +59,7 @@ extends AnyFreeSpecLike, lazy val tdAll: TdAll = TdAll.tdAll lazy val frozenInstant: Instant = tdAll.instant - private val zoneId: ZoneId = ZoneId.of("UTC") - lazy val clock: Clock = Clock.fixed(frozenInstant, zoneId) + lazy val clock: Clock = Clock.fixed(frozenInstant, TdZoneId.zoneId) protected def configMap: Map[String, Any] = Map[String, Any]( @@ -65,6 +67,7 @@ extends AnyFreeSpecLike, "auditing.enabled" -> false, "auditing.traceRequests" -> false, "microservice.services.auth.port" -> WireMockSupport.port, + "microservice.services.email.port" -> WireMockSupport.port, "mongodb.uri" -> mongoUri, "microservice.services.object-store.port" -> WireMockSupport.port, "microservice.services.secure-data-exchange-proxy.host" -> "localhost", @@ -104,10 +107,5 @@ extends AnyFreeSpecLike, ) sc.copy(configuration = sc.configuration.withFallback(overrideServerConfiguration(app))) - override def beforeEach(): Unit = { - super.beforeEach() - dropDatabase() - } - object ISpec: val testServerPort: Int = 19003 diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdAll.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdAll.scala index d946c1b..bc5df34 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdAll.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdAll.scala @@ -32,11 +32,15 @@ import uk.gov.hmrc.agentregistration.shared.testdata.TdBase import uk.gov.hmrc.agentregistration.shared.testdata.providedetails.individual.TdIndividualProvidedDetails import uk.gov.hmrc.agentregistration.shared.testdata.agentapplication.TdAgentApplicationLlp import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName +import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo +import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.sdes.TdRiskingRecords import java.time.Instant +import java.time.ZoneId import java.time.temporal.ChronoUnit import java.time.temporal.TemporalUnit @@ -58,51 +62,6 @@ import java.time.temporal.TemporalUnit // override def instant: Instant = Instant.parse("2059-11-26T16:33:51Z") // override def riskingFileName: RiskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591126_163351.txt") -object TdRisking: - - def make( - instant: Instant, - agentApplication: AgentApplication, - personReferencePrefix: String, - riskingFileName: RiskingFileName - ): TdRisking = - val instantParam: Instant = instant - val agentApplicationParam: AgentApplication = agentApplication - val personReferencePrefixParam: String = personReferencePrefix - val riskingFileNameParam: RiskingFileName = riskingFileName - new TdRisking: - override def instant: Instant = instantParam - override def agentApplication: AgentApplication = agentApplicationParam - override def personReferencePrefix: String = personReferencePrefixParam - override def riskingFileName: RiskingFileName = riskingFileNameParam - -trait TdRisking: - - def agentApplication: AgentApplication - def personReferencePrefix: String - def instant: Instant - def riskingFileName: RiskingFileName - - def tdApplicationForRisking: TdApplicationForRisking = TdApplicationForRisking.make( - instant = instant, - riskingFileName = riskingFileName, - agentApplication = agentApplication - ) - - def tdIndividualsForRisking: TdIndividualsForRisking = TdIndividualsForRisking.make( - instantParam = instant, - personReferencePrefixParam = personReferencePrefix, - applicationReferenceParam = agentApplication.applicationReference - ) - - def submitForRiskingRequest: SubmitForRiskingRequest = SubmitForRiskingRequest( - agentApplication = agentApplication, - individuals = List( - tdIndividualsForRisking.tdIndividualForRisking1.readyForSubmission.individualProvidedDetails, - tdIndividualsForRisking.tdIndividualForRisking2.readyForSubmission.individualProvidedDetails - ) - ) - object TdAll: def apply(): TdAll = new TdAll {} @@ -117,48 +76,10 @@ extends TdRiskingBase, TdObjectStore, sdes.TdSdesData, TdSdesProxy, - TdRiskingRecords: - - val tdRisking: TdRisking = TdRisking.make( - instant = Instant.parse("2059-11-25T16:33:51Z"), - agentApplication = - TdApplicationsFactory - .make(ApplicationReference("APPGENPAR1")) - .agentApplicationGeneralPartnership - .afterDeclarationSubmitted, - personReferencePrefix = "PREFGENP", - riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591125_163351.txt") - ) - - val tdRisking2: TdRisking = TdRisking.make( - instant = Instant.parse("2059-11-26T16:33:51Z"), - agentApplication = - TdApplicationsFactory - .make(ApplicationReference("APPSOLTRA1")) - .agentApplicationSoleTrader - .afterDeclarationSubmitted, - personReferencePrefix = "PREFSOLT", - riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591126_163351.txt") - ) + TdRiskingRecords, + TdEmail: - val tdRisking3: TdRisking = TdRisking.make( - instant = Instant.parse("2059-11-27T16:33:51Z"), - agentApplication = - TdApplicationsFactory - .make(ApplicationReference("APPLLPART1")) - .agentApplicationLlp - .afterDeclarationSubmitted, - personReferencePrefix = "PREFLLPA", - riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591127_163351.txt") - ) + val tdRiskingInstancesInStates: TdRiskingInstancesInStates.type = TdRiskingInstancesInStates - val tdRisking4: TdRisking = TdRisking.make( - instant = Instant.parse("2059-11-28T16:33:51Z"), - agentApplication = - TdApplicationsFactory - .make(ApplicationReference("APPSCOPAR1")) - .agentApplicationScottishPartnership - .afterDeclarationSubmitted, - personReferencePrefix = "PREFSCOP", - riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591128_163351.txt") - ) +object TdZoneId: + val zoneId: ZoneId = ZoneId.of("UTC") diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdApplicationForRisking.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdApplicationForRisking.scala index 4fa204a..2a16f47 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdApplicationForRisking.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdApplicationForRisking.scala @@ -20,10 +20,13 @@ import uk.gov.hmrc.agentregistration.shared.AgentApplication import uk.gov.hmrc.agentregistration.shared.ApplicationReference import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking import uk.gov.hmrc.agentregistrationrisking.model.EntityRiskingResult +import uk.gov.hmrc.agentregistrationrisking.model.OverallStatus import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName +import uk.gov.hmrc.agentregistrationrisking.model.RiskingOutcome import java.time.Instant import java.time.temporal.ChronoUnit +import com.softwaremill.quicklens.modify object TdApplicationForRisking: @@ -57,7 +60,11 @@ trait TdApplicationForRisking: lastUpdatedAt = instant, entityRiskingResult = None, isSubscribed = false, - isEmailSent = false + isEmailSent = false, + overallStatus = OverallStatus( + riskingOutcome = None, + emailsProcessed = false + ) ) def submittedForRisking: ApplicationForRisking = readyForSubmission @@ -75,6 +82,18 @@ trait TdApplicationForRisking: )) ) + val approvedAfterOutcome: ApplicationForRisking = approved + .modify(_.overallStatus.riskingOutcome) + .setTo(Some(RiskingOutcome.Approved)) + + val approvedAfterSubscribed: ApplicationForRisking = approvedAfterOutcome.copy(isSubscribed = true) + + val approvedAfterEmailSent: ApplicationForRisking = approvedAfterSubscribed + .copy(isEmailSent = true) + + val approvedAfterEmailsProcessed: ApplicationForRisking = approvedAfterEmailSent + .modify(_.overallStatus.emailsProcessed).setTo(true) + val failedFixable: ApplicationForRisking = submittedForRisking.copy( entityRiskingResult = Some(EntityRiskingResult( failures = List( @@ -85,12 +104,29 @@ trait TdApplicationForRisking: )) ) - val failedNonFixable: ApplicationForRisking = submittedForRisking.copy( - entityRiskingResult = Some(EntityRiskingResult( - failures = List( - TdFailures.entityFailures.fixable2, - TdFailures.entityFailures.nonFixable2 - ), - receivedAt = instant.minus(2, ChronoUnit.DAYS) - )) + val failedFixableAfterOutcome: ApplicationForRisking = failedFixable + .modify(_.overallStatus.riskingOutcome) + .setTo(Some(RiskingOutcome.FailedFixable)) + + val failedNonFixable: ApplicationForRisking = submittedForRisking + .copy( + entityRiskingResult = Some(EntityRiskingResult( + failures = List( + TdFailures.entityFailures.fixable2, + TdFailures.entityFailures.nonFixable2 + ), + receivedAt = instant.minus(2, ChronoUnit.DAYS) + )) + ) + + val failedNonFixableAfterOutcome: ApplicationForRisking = failedNonFixable + .modify(_.overallStatus.riskingOutcome) + .setTo(Some(RiskingOutcome.FailedNonFixable)) + + val failedNonFixableAfterEmailSent: ApplicationForRisking = failedNonFixableAfterOutcome.copy( + isEmailSent = true ) + + val failedNonFixableAfterEmailsProcessed: ApplicationForRisking = failedNonFixableAfterEmailSent + .modify(_.overallStatus.emailsProcessed) + .setTo(true) diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdEmail.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdEmail.scala new file mode 100644 index 0000000..43f3b37 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdEmail.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata + +import uk.gov.hmrc.agentregistration.shared.EmailAddress +import uk.gov.hmrc.agentregistrationrisking.model.EmailTemplateId +import uk.gov.hmrc.agentregistrationrisking.model.SendEmailRequest + +trait TdEmail: + + val sendEmailRequest: SendEmailRequest = SendEmailRequest( + to = Seq(EmailAddress("agent@example.com")), + templateId = EmailTemplateId.RegistrationSuccess, + parameters = Map( + "agencyName" -> "Test Agency", + "arn" -> "TARN0000001" + ) + ) diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdIndividualForRisking.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdIndividualForRisking.scala index 6b8dba2..21183c5 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdIndividualForRisking.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdIndividualForRisking.scala @@ -54,7 +54,8 @@ trait TdIndividualForRisking: individualProvidedDetails = individualProvidedDetails, createdAt = instant, lastUpdatedAt = instant, - individualRiskingResult = None + individualRiskingResult = None, + isEmailSent = false ) // nothing changes from data perspective @@ -79,7 +80,7 @@ trait TdIndividualForRisking: )) ) - def applicationFailedNonFixable: IndividualForRisking = submittedForRisking.copy( + def failedNonFixable: IndividualForRisking = submittedForRisking.copy( individualRiskingResult = Some(IndividualRiskingResult( failures = List( TdFailures.individualFailures.fixable2, @@ -88,3 +89,5 @@ trait TdIndividualForRisking: receivedAt = instant.minus(2, ChronoUnit.DAYS) )) ) + + def failedNonFixableEmailSent: IndividualForRisking = failedNonFixable.copy(isEmailSent = true) diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRisking.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRisking.scala new file mode 100644 index 0000000..d75b8f0 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRisking.scala @@ -0,0 +1,89 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata + +import uk.gov.hmrc.agentregistration.shared.AgentApplication +import uk.gov.hmrc.agentregistration.shared.AgentApplicationLlp +import uk.gov.hmrc.agentregistration.shared.ApplicationReference +import uk.gov.hmrc.agentregistration.shared.risking.SubmitForRiskingRequest +import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName + +import java.time.Instant +import scala.util.Random + +trait TdRisking: + + def agentApplication: AgentApplication + def personReferencePrefix: String + def instant: Instant + def riskingFileName: RiskingFileName + + def tdApplicationForRisking: TdApplicationForRisking = TdApplicationForRisking.make( + instant = instant, + riskingFileName = riskingFileName, + agentApplication = agentApplication + ) + + def tdIndividualsForRisking: TdIndividualsForRisking = TdIndividualsForRisking.make( + instantParam = instant, + personReferencePrefixParam = personReferencePrefix, + applicationReferenceParam = agentApplication.applicationReference + ) + + def submitForRiskingRequest: SubmitForRiskingRequest = SubmitForRiskingRequest( + agentApplication = agentApplication, + individuals = List( + tdIndividualsForRisking.tdIndividualForRisking1.readyForSubmission.individualProvidedDetails, + tdIndividualsForRisking.tdIndividualForRisking2.readyForSubmission.individualProvidedDetails + ) + ) + +object TdRisking: + + def make( + instant: Instant, + agentApplication: AgentApplication, + personReferencePrefix: String, + riskingFileName: RiskingFileName + ): TdRisking = + val instantParam: Instant = instant + val agentApplicationParam: AgentApplication = agentApplication + val personReferencePrefixParam: String = personReferencePrefix + val riskingFileNameParam: RiskingFileName = riskingFileName + new TdRisking: + override def instant: Instant = instantParam + override def agentApplication: AgentApplication = agentApplicationParam + override def personReferencePrefix: String = personReferencePrefixParam + override def riskingFileName: RiskingFileName = riskingFileNameParam + + def make( + seed: String + ): TdRisking = + val random: Random = new scala.util.Random(seed.hashCode) + val instant: Instant = Instant.parse("2059-11-26T16:33:51Z").plusSeconds(random.nextInt(1000000)) + val application: AgentApplicationLlp = + TdApplicationsFactory + .make(ApplicationReference(s"APPREF_$seed")) + .agentApplicationLlp + .afterDeclarationSubmitted + + make( + instant = instant, + agentApplication = application, + personReferencePrefix = s"PREF_$seed", + riskingFileName = RiskingFileName.make(instant) + ) diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRiskingInstances.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRiskingInstances.scala new file mode 100644 index 0000000..a3e0abb --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRiskingInstances.scala @@ -0,0 +1,112 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata + +import uk.gov.hmrc.agentregistration.shared.ApplicationReference +import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName + +import java.time.Instant + +object TdRiskingInstances: + + val tdRisking: TdRisking = TdRisking.make( + instant = Instant.parse("2059-11-25T16:33:51Z"), + agentApplication = + TdApplicationsFactory + .make(ApplicationReference("APPGENPAR1")) + .agentApplicationGeneralPartnership + .afterDeclarationSubmitted, + personReferencePrefix = "PREFGENP", + riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591125_163351.txt") + ) + + val tdRisking2: TdRisking = TdRisking.make( + instant = Instant.parse("2059-11-26T16:33:51Z"), + agentApplication = + TdApplicationsFactory + .make(ApplicationReference("APPSOLTRA1")) + .agentApplicationSoleTrader + .afterDeclarationSubmitted, + personReferencePrefix = "PREFSOLT", + riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591126_163351.txt") + ) + + val tdRisking3: TdRisking = TdRisking.make( + instant = Instant.parse("2059-11-27T16:33:51Z"), + agentApplication = + TdApplicationsFactory + .make(ApplicationReference("APPLLPART1")) + .agentApplicationLlp + .afterDeclarationSubmitted, + personReferencePrefix = "PREFLLPA", + riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591127_163351.txt") + ) + + val tdRisking4: TdRisking = TdRisking.make( + instant = Instant.parse("2059-11-28T16:33:51Z"), + agentApplication = + TdApplicationsFactory + .make(ApplicationReference("APPSCOPAR1")) + .agentApplicationScottishPartnership + .afterDeclarationSubmitted, + personReferencePrefix = "PREFSCOP", + riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591128_163351.txt") + ) + + val tdRisking5: TdRisking = TdRisking.make( + instant = Instant.parse("2059-11-29T16:33:51Z"), + agentApplication = + TdApplicationsFactory + .make(ApplicationReference("APPLTDART1")) + .agentApplicationLimitedCompany + .afterDeclarationSubmitted, + personReferencePrefix = "PREFLTDA", + riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591129_163351.txt") + ) + + val tdRisking6: TdRisking = TdRisking.make( + instant = Instant.parse("2059-11-30T16:33:51Z"), + agentApplication = + TdApplicationsFactory + .make(ApplicationReference("APPSOLTRR1")) + .agentApplicationSoleTraderRepresentative + .afterDeclarationSubmitted, + personReferencePrefix = "PREFSOLR", + riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591130_163351.txt") + ) + + val tdRisking7: TdRisking = TdRisking.make( + instant = Instant.parse("2059-12-01T16:33:51Z"), + agentApplication = + TdApplicationsFactory + .make(ApplicationReference("APPSCOTLP1")) + .agentApplicationScottishLimitedPartnership + .afterDeclarationSubmitted, + personReferencePrefix = "PREFSCLP", + riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591201_163351.txt") + ) + + val tdRisking8: TdRisking = TdRisking.make( + instant = Instant.parse("2059-12-02T16:33:51Z"), + agentApplication = + TdApplicationsFactory + .make(ApplicationReference("APPSCOTLP2")) + .agentApplicationScottishLimitedPartnership + .afterDeclarationSubmitted, + personReferencePrefix = "PREFSCP2", + riskingFileName = RiskingFileName("asa_risking_file_version1_0_4_20591202_163351.txt") + ) diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRiskingInstancesInStates.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRiskingInstancesInStates.scala new file mode 100644 index 0000000..fa45d05 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/TdRiskingInstancesInStates.scala @@ -0,0 +1,412 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata + +import com.softwaremill.quicklens.modify +import uk.gov.hmrc.agentregistration.shared.risking.RiskedEntity +import uk.gov.hmrc.agentregistration.shared.risking.RiskedIndividual +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.model.RiskingOutcome +import uk.gov.hmrc.agentregistrationrisking.testsupport.RichMatchers.* + +import java.time.LocalDate +import java.time.temporal.ChronoUnit + +trait TdApplicationWithIndividuals: + + def tdRisking: TdRisking + def application: ApplicationForRisking = tdRisking.tdApplicationForRisking.readyForSubmission + def individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.readyForSubmission + def individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.readyForSubmission + def applicationWithIndividuals: ApplicationWithIndividuals = ApplicationWithIndividuals( + application = application, + individuals = Seq(individual1, individual2) + ) + def riskingProgressForApplicant: RiskingProgress + +//TODO +// def riskingProgressForIndividual1: RiskingProgress +// def riskingProgressForIndividual2: RiskingProgress + +object TdRiskingInstancesInStates: + + val all: Seq[TdApplicationWithIndividuals] = Seq( + readyForSubmission, + readyForSubmission2, + submittedForRisking, + partiallyRisked.approved_approved_submitted, + partiallyRisked.approved_failedFixable_submitted, + partiallyRisked.approved_failedNonFixable_submitted, + partiallyRisked.approved_submitted_submitted, + partiallyRisked.failedFixable_approved_submitted, + partiallyRisked.failedFixable_failedFixable_submitted, + partiallyRisked.failedFixable_failedNonFixable_submitted, + partiallyRisked.failedFixable_submitted_submitted, + partiallyRisked.failedNonFixable_approved_submitted, + partiallyRisked.failedNonFixable_failedFixable_submitted, + partiallyRisked.failedNonFixable_failedNonFixable_submitted, + partiallyRisked.failedNonFixable_submitted_submitted, + partiallyRisked.submitted_approved_submitted, + partiallyRisked.submitted_failedFixable_submitted, + partiallyRisked.submitted_failedNonFixable_submitted, + approved, + approvedAfterOutcome, + approvedAfterSubscribed, + approvedAfterEmailSent, + approvedAfterEmailsProcessed, + failedFixable, + failedFixableAfterOutcome, + failedNonFixable, + failedNonFixableAfterOutcome, + failedNonFixableAfter1EmailSent, + failedNonFixableAfter2EmailsSent, + failedNonFixableAfterAllEmailsSent, + failedNonFixableAfterAllEmailsProcessed + ) + + case object readyForSubmission + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRiskingInstances.tdRisking + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.readyForSubmission + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.readyForSubmission + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.readyForSubmission + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.ReadyForSubmission + + case object readyForSubmission2 + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.readyForSubmission + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.readyForSubmission + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.readyForSubmission + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.ReadyForSubmission + + case object submittedForRisking + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRiskingInstances.tdRisking2 + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.submittedForRisking + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.submittedForRisking + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking + + case object partiallyRisked: + + val approved_approved_submitted = partiallyrisked.approved_approved_submitted + val approved_failedFixable_submitted = partiallyrisked.approved_failedFixable_submitted + val approved_failedNonFixable_submitted = partiallyrisked.approved_failedNonFixable_submitted + val approved_submitted_submitted = partiallyrisked.approved_submitted_submitted + val failedFixable_approved_submitted = partiallyrisked.failedFixable_approved_submitted + val failedFixable_failedFixable_submitted = partiallyrisked.failedFixable_failedFixable_submitted + val failedFixable_failedNonFixable_submitted = partiallyrisked.failedFixable_failedNonFixable_submitted + val failedFixable_submitted_submitted = partiallyrisked.failedFixable_submitted_submitted + val failedNonFixable_approved_submitted = partiallyrisked.failedNonFixable_approved_submitted + val failedNonFixable_failedFixable_submitted = partiallyrisked.failedNonFixable_failedFixable_submitted + val failedNonFixable_failedNonFixable_submitted = partiallyrisked.failedNonFixable_failedNonFixable_submitted + val failedNonFixable_submitted_submitted = partiallyrisked.failedNonFixable_submitted_submitted + val submitted_approved_submitted = partiallyrisked.submitted_approved_submitted + val submitted_failedFixable_submitted = partiallyrisked.submitted_failedFixable_submitted + val submitted_failedNonFixable_submitted = partiallyrisked.submitted_failedNonFixable_submitted + + case object approved + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.Approved + + case object approvedAfterOutcome + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRiskingInstances.tdRisking3 + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approvedAfterOutcome + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.Approved + + case object approvedAfterSubscribed + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRiskingInstances.tdRisking7 + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approvedAfterSubscribed + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.Approved + + case object approvedAfterEmailSent + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRiskingInstances.tdRisking8 + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approvedAfterEmailSent + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.Approved + + case object approvedAfterEmailsProcessed + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approvedAfterEmailsProcessed + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.Approved + + case object failedFixable + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRiskingInstances.tdRisking4 + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override val riskingProgressForApplicant: RiskingProgress.FailedFixable = RiskingProgress.FailedFixable( + riskedEntity = RiskedEntity( + applicationReference = application.applicationReference, + failures = Seq.empty + ), + riskedIndividuals = Seq( + RiskedIndividual( + personReference = individual1.personReference, + individualName = individual1.individualProvidedDetails.individualName, + failures = individual1.individualRiskingResult.value.failures + ), + RiskedIndividual( + personReference = individual2.personReference, + individualName = individual2.individualProvidedDetails.individualName, + failures = individual2.individualRiskingResult.value.failures + ) + ), + riskingCompletedDate = LocalDate.parse("2059-11-26") + ) + + case object failedFixableAfterOutcome + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approvedAfterOutcome + .modify(_.overallStatus.riskingOutcome) + .setTo(Some(RiskingOutcome.FailedFixable)) + + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override val riskingProgressForApplicant: RiskingProgress.FailedFixable = RiskingProgress.FailedFixable( + riskedEntity = RiskedEntity( + applicationReference = application.applicationReference, + failures = Seq.empty + ), + riskedIndividuals = Seq( + RiskedIndividual( + personReference = individual1.personReference, + individualName = individual1.individualProvidedDetails.individualName, + failures = individual1.individualRiskingResult.value.failures + ), + RiskedIndividual( + personReference = individual2.personReference, + individualName = individual2.individualProvidedDetails.individualName, + failures = individual2.individualRiskingResult.value.failures + ) + ), + riskingCompletedDate = LocalDate.parse("2059-11-28") + ) + + case object failedNonFixable + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixable + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override val riskingProgressForApplicant: RiskingProgress.FailedNonFixable = RiskingProgress.FailedNonFixable( + riskedEntity = RiskedEntity( + applicationReference = application.applicationReference, + failures = application.entityRiskingResult.value.failures + ), + riskedIndividuals = Seq( + RiskedIndividual( + personReference = individual1.personReference, + individualName = individual1.individualProvidedDetails.individualName, + failures = individual1.individualRiskingResult.value.failures + ), + RiskedIndividual( + personReference = individual2.personReference, + individualName = individual2.individualProvidedDetails.individualName, + failures = individual2.individualRiskingResult.value.failures + ) + ), + riskingCompletedDate = LocalDate.parse("2059-12-02") + ) + + case object failedNonFixableAfterOutcome + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRiskingInstances.tdRisking5 + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixableAfterOutcome + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.approved + + override val riskingProgressForApplicant: RiskingProgress.FailedNonFixable = RiskingProgress.FailedNonFixable( + riskedEntity = RiskedEntity( + applicationReference = application.applicationReference, + failures = application.entityRiskingResult.value.failures + ), + riskedIndividuals = Seq( + RiskedIndividual( + personReference = individual1.personReference, + individualName = individual1.individualProvidedDetails.individualName, + failures = individual1.individualRiskingResult.value.failures + ), + RiskedIndividual( + personReference = individual2.personReference, + individualName = individual2.individualProvidedDetails.individualName, + failures = individual2.individualRiskingResult.value.failures + ) + ), + riskingCompletedDate = LocalDate.parse("2059-11-27") + ) + + case object failedNonFixableAfter1EmailSent + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixableAfterEmailSent + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedNonFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.failedNonFixable + + override val riskingProgressForApplicant: RiskingProgress.FailedNonFixable = RiskingProgress.FailedNonFixable( + riskedEntity = RiskedEntity( + applicationReference = application.applicationReference, + failures = application.entityRiskingResult.value.failures + ), + riskedIndividuals = Seq( + RiskedIndividual( + personReference = individual1.personReference, + individualName = individual1.individualProvidedDetails.individualName, + failures = individual1.individualRiskingResult.value.failures + ), + RiskedIndividual( + personReference = individual2.personReference, + individualName = individual2.individualProvidedDetails.individualName, + failures = individual2.individualRiskingResult.value.failures + ) + ), + riskingCompletedDate = LocalDate.parse("2059-11-25") + ) + + case object failedNonFixableAfter2EmailsSent + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixableAfterEmailSent + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedNonFixableEmailSent + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.failedNonFixable + + override val riskingProgressForApplicant: RiskingProgress.FailedNonFixable = RiskingProgress.FailedNonFixable( + riskedEntity = RiskedEntity( + applicationReference = application.applicationReference, + failures = application.entityRiskingResult.value.failures + ), + riskedIndividuals = Seq( + RiskedIndividual( + personReference = individual1.personReference, + individualName = individual1.individualProvidedDetails.individualName, + failures = individual1.individualRiskingResult.value.failures + ), + RiskedIndividual( + personReference = individual2.personReference, + individualName = individual2.individualProvidedDetails.individualName, + failures = individual2.individualRiskingResult.value.failures + ) + ), + riskingCompletedDate = LocalDate.parse("2059-12-05") + ) + + case object failedNonFixableAfterAllEmailsSent + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixableAfterEmailSent + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedNonFixableEmailSent + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.failedNonFixableEmailSent + + override val riskingProgressForApplicant: RiskingProgress.FailedNonFixable = RiskingProgress.FailedNonFixable( + riskedEntity = RiskedEntity( + applicationReference = application.applicationReference, + failures = application.entityRiskingResult.value.failures + ), + riskedIndividuals = Seq( + RiskedIndividual( + personReference = individual1.personReference, + individualName = individual1.individualProvidedDetails.individualName, + failures = individual1.individualRiskingResult.value.failures + ), + RiskedIndividual( + personReference = individual2.personReference, + individualName = individual2.individualProvidedDetails.individualName, + failures = individual2.individualRiskingResult.value.failures + ) + ), + riskingCompletedDate = LocalDate.parse("2059-12-05") + ) + + case object failedNonFixableAfterAllEmailsProcessed + extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = + tdRisking + .tdApplicationForRisking + .receivedRiskingResults + .failedNonFixableAfterEmailsProcessed + + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedNonFixableEmailSent + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.receivedRiskingResults.failedNonFixableEmailSent + + override val riskingProgressForApplicant: RiskingProgress.FailedNonFixable = RiskingProgress.FailedNonFixable( + riskedEntity = RiskedEntity( + applicationReference = application.applicationReference, + failures = application.entityRiskingResult.value.failures + ), + riskedIndividuals = Seq( + RiskedIndividual( + personReference = individual1.personReference, + individualName = individual1.individualProvidedDetails.individualName, + failures = individual1.individualRiskingResult.value.failures + ), + RiskedIndividual( + personReference = individual2.personReference, + individualName = individual2.individualProvidedDetails.individualName, + failures = individual2.individualRiskingResult.value.failures + ) + ), + riskingCompletedDate = LocalDate.parse("2059-11-26") + ) diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_approved_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_approved_submitted.scala new file mode 100644 index 0000000..2b12544 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_approved_submitted.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking + +case object approved_approved_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_failedFixable_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_failedFixable_submitted.scala new file mode 100644 index 0000000..8453473 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_failedFixable_submitted.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object approved_failedFixable_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_failedNonFixable_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_failedNonFixable_submitted.scala new file mode 100644 index 0000000..785d484 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_failedNonFixable_submitted.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object approved_failedNonFixable_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedNonFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_submitted_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_submitted_submitted.scala new file mode 100644 index 0000000..804fc16 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/approved_submitted_submitted.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object approved_submitted_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.approved + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.submittedForRisking + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_approved_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_approved_submitted.scala new file mode 100644 index 0000000..cfecd7d --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_approved_submitted.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object failedFixable_approved_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedFixableAfterOutcome + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_failedFixable_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_failedFixable_submitted.scala new file mode 100644 index 0000000..4d9b383 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_failedFixable_submitted.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object failedFixable_failedFixable_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedFixableAfterOutcome + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_failedNonFixable_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_failedNonFixable_submitted.scala new file mode 100644 index 0000000..5ca982d --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_failedNonFixable_submitted.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object failedFixable_failedNonFixable_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedFixableAfterOutcome + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedNonFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_submitted_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_submitted_submitted.scala new file mode 100644 index 0000000..b4b28c7 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedFixable_submitted_submitted.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object failedFixable_submitted_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedFixableAfterOutcome + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.submittedForRisking + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_approved_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_approved_submitted.scala new file mode 100644 index 0000000..2dff5bf --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_approved_submitted.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object failedNonFixable_approved_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixable + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_failedFixable_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_failedFixable_submitted.scala new file mode 100644 index 0000000..b70991b --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_failedFixable_submitted.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object failedNonFixable_failedFixable_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixable + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_failedNonFixable_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_failedNonFixable_submitted.scala new file mode 100644 index 0000000..20b126e --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_failedNonFixable_submitted.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +case object failedNonFixable_failedNonFixable_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixable + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedNonFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_submitted_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_submitted_submitted.scala new file mode 100644 index 0000000..0fbe6d3 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/failedNonFixable_submitted_submitted.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking + +case object failedNonFixable_submitted_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.receivedRiskingResults.failedNonFixable + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.submittedForRisking + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_approved_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_approved_submitted.scala new file mode 100644 index 0000000..643b711 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_approved_submitted.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking + +case object submitted_approved_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.submittedForRisking + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.approved + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_failedFixable_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_failedFixable_submitted.scala new file mode 100644 index 0000000..c9af5e4 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_failedFixable_submitted.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress + +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking + +case object submitted_failedFixable_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.submittedForRisking + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_failedNonFixable_submitted.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_failedNonFixable_submitted.scala new file mode 100644 index 0000000..0c716a1 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/testdata/partiallyrisked/submitted_failedNonFixable_submitted.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.testdata.partiallyrisked + +import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking +import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdApplicationWithIndividuals +import uk.gov.hmrc.agentregistrationrisking.testsupport.testdata.TdRisking + +case object submitted_failedNonFixable_submitted +extends TdApplicationWithIndividuals: + + override val tdRisking: TdRisking = TdRisking.make(this.toString) + override val application: ApplicationForRisking = tdRisking.tdApplicationForRisking.submittedForRisking + override val individual1: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking1.receivedRiskingResults.failedNonFixable + override val individual2: IndividualForRisking = tdRisking.tdIndividualsForRisking.tdIndividualForRisking2.submittedForRisking + override def riskingProgressForApplicant: RiskingProgress = RiskingProgress.SubmittedForRisking diff --git a/test/uk/gov/hmrc/agentregistrationrisking/testsupport/wiremock/stubs/EmailStubs.scala b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/wiremock/stubs/EmailStubs.scala new file mode 100644 index 0000000..7791670 --- /dev/null +++ b/test/uk/gov/hmrc/agentregistrationrisking/testsupport/wiremock/stubs/EmailStubs.scala @@ -0,0 +1,48 @@ +/* + * Copyright 2026 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.agentregistrationrisking.testsupport.wiremock.stubs + +import com.github.tomakehurst.wiremock.client.WireMock as wm +import com.github.tomakehurst.wiremock.client.WireMock.equalToJson +import com.github.tomakehurst.wiremock.stubbing.StubMapping +import play.api.libs.json.Json +import uk.gov.hmrc.agentregistrationrisking.model.SendEmailRequest +import uk.gov.hmrc.agentregistrationrisking.testsupport.wiremock.StubMaker + +object EmailStubs: + + private val sendEmailUrl: String = "/hmrc/email" + + def stubSendEmail(sendEmailRequest: SendEmailRequest): StubMapping = stub(sendEmailRequest, responseStatus = 202) + + def stubSendEmailFailure(sendEmailRequest: SendEmailRequest): StubMapping = stub(sendEmailRequest, responseStatus = 500) + + private def stub( + sendEmailRequest: SendEmailRequest, + responseStatus: Int + ): StubMapping = StubMaker.make( + httpMethod = StubMaker.HttpMethod.POST, + urlPattern = wm.urlEqualTo(sendEmailUrl), + requestBody = Some(equalToJson(Json.prettyPrint(Json.toJson(sendEmailRequest)))), + responseStatus = responseStatus + ) + + def verifySendEmail(count: Int = 1): Unit = StubMaker.verify( + httpMethod = StubMaker.HttpMethod.POST, + urlPattern = wm.urlEqualTo(sendEmailUrl), + count = count + ) diff --git a/test/uk/gov/hmrc/agentregistrationrisking/util/ProcessInSequenceSpec.scala b/test/uk/gov/hmrc/agentregistrationrisking/util/ProcessInSequenceSpec.scala index 950039d..ef987d9 100644 --- a/test/uk/gov/hmrc/agentregistrationrisking/util/ProcessInSequenceSpec.scala +++ b/test/uk/gov/hmrc/agentregistrationrisking/util/ProcessInSequenceSpec.scala @@ -99,3 +99,69 @@ extends UnitSpec: result.failed.futureValue shouldBe boom callLog.toList shouldBe List(0, 1, 2) + + "processAllInSequence emptySeq returns 0 and does not call f or onFailure" in: + val callLog = mutable.ListBuffer.empty[Int] + val failureLog = mutable.ListBuffer.empty[(Throwable, Int)] + val result = + ProcessInSequence.processAllInSequence(Seq.empty[Int])(item => { callLog += item; Future.successful(item) })((ex, item) => failureLog += ((ex, item))) + + result.futureValue shouldBe 0 + callLog shouldBe empty + failureLog shouldBe empty + + "processAllInSequence allSucceed returns a count equal to the number of items" in: + val result = ProcessInSequence.processAllInSequence(Seq(1, 2, 3))(item => Future.successful(item))((_, _) => ()) + + result.futureValue shouldBe 3 + + "processAllInSequence singleFailure calls onFailure with the correct exception and item, returns 0" in: + val failureLog = mutable.ListBuffer.empty[(Throwable, Int)] + val boom = new RuntimeException("boom") + val result = ProcessInSequence.processAllInSequence(Seq(42))(_ => Future.failed(boom))((ex, item) => failureLog += ((ex, item))) + + result.futureValue shouldBe 0 + failureLog.toList shouldBe List((boom, 42)) + + "processAllInSequence someFailures continues processing after failures and returns count of successes" in: + val callLog = mutable.ListBuffer.empty[Int] + val failureLog = mutable.ListBuffer.empty[Int] + val boom = new RuntimeException("boom") + val result = + ProcessInSequence.processAllInSequence(Seq(0, 1, 2, 3, 4))(item => + callLog += item + if item % 2 == 1 then Future.failed(boom) else Future.successful(item) + )((_, item) => failureLog += item) + + result.futureValue shouldBe 3 + callLog.toList shouldBe List(0, 1, 2, 3, 4) + failureLog.toList shouldBe List(1, 3) + + "processAllInSequence allFail calls onFailure for every item and returns 0" in: + val failureLog = mutable.ListBuffer.empty[Int] + val boom = new RuntimeException("boom") + val result = ProcessInSequence.processAllInSequence(Seq(0, 1, 2))(item => Future.failed(boom))((_, item) => failureLog += item) + + result.futureValue shouldBe 0 + failureLog.toList shouldBe List(0, 1, 2) + + "processAllInSequence sequencing processes items in order even when some fail" in: + val callLog = mutable.ListBuffer.empty[String] + val boom = new RuntimeException("boom") + + ProcessInSequence.processAllInSequence(Seq(0, 1, 2))(item => + Future: + callLog += s"Started $item" + Thread.sleep(if item == 1 then 60 else 20) + callLog += s"Finished $item" + if item == 1 then throw boom else item + )((_, _) => ()).futureValue + + callLog.toList shouldBe List( + "Started 0", + "Finished 0", + "Started 1", + "Finished 1", + "Started 2", + "Finished 2" + )