From 4c88f49507e7cccef4ebfafe57f28a977de595af Mon Sep 17 00:00:00 2001 From: petekirby-ee Date: Wed, 11 Dec 2024 15:48:21 +0000 Subject: [PATCH] APIS-7388 Add new field deleteRestriction to application (#532) * APIS-7388 Add new field deleteRestriction to application * APIS-7388 Add new field deleteRestriction to application * APIS-7388 Set value of DeleteRestriction * APIS-7388 Set value of DeleteRestriction * APIS-7388 Set value of DeleteRestriction * APIS-7388 Use latest application/event domain library * APIS-7388 Make SetDeleteRestrictionJob set DELETED apps also --- .../config/ConfigurationProviders.scala | 15 ++- .../config/Scheduler.scala | 4 +- .../ApiPlatformEventsConnector.scala | 43 ++++++- .../models/db/StoredApplication.scala | 6 +- .../repository/ApplicationRepository.scala | 10 +- .../scheduled/SetDeleteRestrictionJob.scala | 105 ++++++++++++++++ conf/application.conf | 6 + .../ApplicationRepositoryISpec.scala | 38 ++++-- project/AppDependencies.scala | 4 +- .../SetDeleteRestrictionJobSpec.scala | 118 ++++++++++++++++++ 10 files changed, 325 insertions(+), 24 deletions(-) create mode 100644 app/uk/gov/hmrc/thirdpartyapplication/scheduled/SetDeleteRestrictionJob.scala create mode 100644 test/uk/gov/hmrc/thirdpartyapplication/scheduled/SetDeleteRestrictionJobSpec.scala diff --git a/app/uk/gov/hmrc/thirdpartyapplication/config/ConfigurationProviders.scala b/app/uk/gov/hmrc/thirdpartyapplication/config/ConfigurationProviders.scala index 9fc9723e7..0f2ab8af5 100644 --- a/app/uk/gov/hmrc/thirdpartyapplication/config/ConfigurationProviders.scala +++ b/app/uk/gov/hmrc/thirdpartyapplication/config/ConfigurationProviders.scala @@ -60,7 +60,8 @@ class ConfigurationModule extends Module { bind[ClientSecretsHashingConfig].toProvider[ClientSecretsHashingConfigProvider], bind[ApplicationNamingService.ApplicationNameValidationConfig].toProvider[ApplicationNameValidationConfigConfigProvider], bind[ResetLastAccessDateJobConfig].toProvider[ResetLastAccessDateJobConfigProvider], - bind[TermsOfUseInvitationConfig].toProvider[TermsOfUseInvitationConfigProvider] + bind[TermsOfUseInvitationConfig].toProvider[TermsOfUseInvitationConfigProvider], + bind[SetDeleteRestrictionJobConfig].toProvider[SetDeleteRestrictionJobConfigProvider] ) } } @@ -378,3 +379,15 @@ class ResetLastAccessDateJobConfigProvider @Inject() (configuration: Configurati ResetLastAccessDateJobConfig(LocalDate.parse(noLastAccessDateBeforeAsString), enabled, dryRun) } } + +@Singleton +class SetDeleteRestrictionJobConfigProvider @Inject() (configuration: Configuration) + extends ServicesConfig(configuration) + with Provider[SetDeleteRestrictionJobConfig] { + + override def get(): SetDeleteRestrictionJobConfig = { + val enabled = configuration.get[Boolean]("setDeleteRestrictionJob.enabled") + + SetDeleteRestrictionJobConfig(enabled) + } +} diff --git a/app/uk/gov/hmrc/thirdpartyapplication/config/Scheduler.scala b/app/uk/gov/hmrc/thirdpartyapplication/config/Scheduler.scala index 8d5939d20..fd058473d 100644 --- a/app/uk/gov/hmrc/thirdpartyapplication/config/Scheduler.scala +++ b/app/uk/gov/hmrc/thirdpartyapplication/config/Scheduler.scala @@ -50,6 +50,7 @@ class Scheduler @Inject() ( responsibleIndividualUpdateVerificationRemovalJob: ResponsibleIndividualUpdateVerificationRemovalJob, termsOfUseInvitationReminderJob: TermsOfUseInvitationReminderJob, termsOfUseInvitationOverdueJob: TermsOfUseInvitationOverdueJob, + setDeleteRestrictionJob: SetDeleteRestrictionJob, override val applicationLifecycle: ApplicationLifecycle, override val application: Application )(implicit val ec: ExecutionContext @@ -66,7 +67,8 @@ class Scheduler @Inject() ( responsibleIndividualUpdateVerificationRemovalJob, responsibleIndividualVerificationSetDefaultTypeJob, termsOfUseInvitationReminderJob, - termsOfUseInvitationOverdueJob + termsOfUseInvitationOverdueJob, + setDeleteRestrictionJob ) .filter(_.isEnabled) ++ Seq(bcryptPerformanceMeasureJob) } diff --git a/app/uk/gov/hmrc/thirdpartyapplication/connector/ApiPlatformEventsConnector.scala b/app/uk/gov/hmrc/thirdpartyapplication/connector/ApiPlatformEventsConnector.scala index 037f79882..8473877d1 100644 --- a/app/uk/gov/hmrc/thirdpartyapplication/connector/ApiPlatformEventsConnector.scala +++ b/app/uk/gov/hmrc/thirdpartyapplication/connector/ApiPlatformEventsConnector.scala @@ -17,18 +17,39 @@ package uk.gov.hmrc.thirdpartyapplication.connector import java.net.URL +import java.time.Instant import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} -import play.api.libs.json.Json +import play.api.libs.json.{Json, OFormat} import uk.gov.hmrc.http.HttpReads.Implicits._ import uk.gov.hmrc.http.client.HttpClientV2 import uk.gov.hmrc.http.{HeaderCarrier, StringContextOps} +import uk.gov.hmrc.apiplatform.modules.common.domain.models.{Actor, ApplicationId} import uk.gov.hmrc.apiplatform.modules.common.services.ApplicationLogger import uk.gov.hmrc.apiplatform.modules.events.applications.domain.models._ import uk.gov.hmrc.apiplatform.modules.events.applications.domain.services.EventsInterServiceCallJsonFormatters._ +case class DisplayEvent( + applicationId: ApplicationId, + eventDateTime: Instant, + actor: Actor, + eventTagDescription: String, + eventType: String, + metaData: List[String] + ) + +object DisplayEvent { + implicit val format: OFormat[DisplayEvent] = Json.format[DisplayEvent] +} + +case class QueryResponse(events: List[DisplayEvent]) + +object QueryResponse { + implicit val format: OFormat[QueryResponse] = Json.format[QueryResponse] +} + object ApiPlatformEventsConnector { case class Config(baseUrl: String, enabled: Boolean) } @@ -45,7 +66,7 @@ class ApiPlatformEventsConnector @Inject() (http: HttpClientV2, config: ApiPlatf implicit val headersWithoutAuthorization: HeaderCarrier = hc.copy(authorization = None) if (config.enabled) { - http.post(addEventURI(uri)) + http.post(eventURI(uri)) .withBody(Json.toJson(event)) .execute[ErrorOr[Unit]] .map { @@ -62,8 +83,24 @@ class ApiPlatformEventsConnector @Inject() (http: HttpClientV2, config: ApiPlatf } } - private def addEventURI(path: String): URL = { + private def eventURI(path: String): URL = { val x = s"$serviceBaseUrl$path" url"$x" } + + def query(appId: ApplicationId, tag: Option[String], actorType: Option[String])(implicit hc: HeaderCarrier): Future[List[DisplayEvent]] = { + val queryParams = + Seq( + tag.map(et => "eventTag" -> et), + actorType.map(at => "actorType" -> at) + ).collect { + case Some((a, b)) => a -> b + } + + http.get(url"${eventURI(applicationEventUri)}/${appId}?$queryParams").execute[Option[QueryResponse]] + .map { + case None => List.empty + case Some(response) => response.events + } + } } diff --git a/app/uk/gov/hmrc/thirdpartyapplication/models/db/StoredApplication.scala b/app/uk/gov/hmrc/thirdpartyapplication/models/db/StoredApplication.scala index 444435be4..67d6ec1ec 100644 --- a/app/uk/gov/hmrc/thirdpartyapplication/models/db/StoredApplication.scala +++ b/app/uk/gov/hmrc/thirdpartyapplication/models/db/StoredApplication.scala @@ -46,7 +46,8 @@ case class StoredApplication( checkInformation: Option[CheckInformation] = None, blocked: Boolean = false, ipAllowlist: IpAllowlist = IpAllowlist(), - allowAutoDelete: Boolean = true + allowAutoDelete: Boolean = true, + deleteRestriction: DeleteRestriction = DeleteRestriction.NoRestriction ) extends HasState with HasAccess with HasCollaborators with HasEnvironment { protected val deployedTo = environment @@ -88,7 +89,8 @@ object StoredApplication { data.blocked, ipAllowlist = data.ipAllowlist, allowAutoDelete = data.allowAutoDelete, - lastActionActor = ActorType.UNKNOWN + lastActionActor = ActorType.UNKNOWN, + deleteRestriction = data.deleteRestriction ), data.collaborators ) diff --git a/app/uk/gov/hmrc/thirdpartyapplication/repository/ApplicationRepository.scala b/app/uk/gov/hmrc/thirdpartyapplication/repository/ApplicationRepository.scala index 5002b4fd0..9881681e0 100644 --- a/app/uk/gov/hmrc/thirdpartyapplication/repository/ApplicationRepository.scala +++ b/app/uk/gov/hmrc/thirdpartyapplication/repository/ApplicationRepository.scala @@ -137,7 +137,8 @@ object ApplicationRepository { (JsPath \ "checkInformation").readNullable[CheckInformation] and ((JsPath \ "blocked").read[Boolean] or Reads.pure(false)) and ((JsPath \ "ipAllowlist").read[IpAllowlist] or Reads.pure(IpAllowlist())) and - ((JsPath \ "allowAutoDelete").read[Boolean] or Reads.pure(true)) + ((JsPath \ "allowAutoDelete").read[Boolean] or Reads.pure(true)) and + ((JsPath \ "deleteRestriction").read[DeleteRestriction] or Reads.pure[DeleteRestriction](DeleteRestriction.NoRestriction)) )(StoredApplication.apply _) implicit val formatStoredApplication: OFormat[StoredApplication] = OFormat(readStoredApplication, Json.writes[StoredApplication]) @@ -702,7 +703,8 @@ class ApplicationRepository @Inject() (mongo: MongoComponent, val metrics: Metri "rateLimitTier", "environment", "blocked", - "allowAutoDelete" + "allowAutoDelete", + "deleteRestriction" ) )) @@ -747,8 +749,8 @@ class ApplicationRepository @Inject() (mongo: MongoComponent, val metrics: Metri def fetchAllWithNoSubscriptions(): Future[List[StoredApplication]] = searchApplications("fetchAllWithNoSubscriptions")(new ApplicationSearch(filters = List(NoAPISubscriptions))).map(_.applications) - def fetchAll(): Future[List[StoredApplication]] = { - val result = searchApplications("fetchAll")(new ApplicationSearch()) + def fetchAll(includeDeleted: Boolean = false): Future[List[StoredApplication]] = { + val result = searchApplications("fetchAll")(new ApplicationSearch(includeDeleted = includeDeleted)) result.map(_.applications) } diff --git a/app/uk/gov/hmrc/thirdpartyapplication/scheduled/SetDeleteRestrictionJob.scala b/app/uk/gov/hmrc/thirdpartyapplication/scheduled/SetDeleteRestrictionJob.scala new file mode 100644 index 000000000..b0c095640 --- /dev/null +++ b/app/uk/gov/hmrc/thirdpartyapplication/scheduled/SetDeleteRestrictionJob.scala @@ -0,0 +1,105 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.thirdpartyapplication.scheduled + +import java.time.Clock +import javax.inject.Inject +import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration} +import scala.concurrent.{ExecutionContext, Future} + +import org.mongodb.scala.model.Updates + +import uk.gov.hmrc.http.HeaderCarrier +import uk.gov.hmrc.mongo.lock.{LockRepository, LockService} +import uk.gov.hmrc.mongo.play.json.Codecs +import uk.gov.hmrc.mongo.play.json.formats.MongoJavatimeFormats + +import uk.gov.hmrc.apiplatform.modules.common.domain.models.{Actors, ApplicationId} +import uk.gov.hmrc.apiplatform.modules.common.services.{ApplicationLogger, ClockNow} +import uk.gov.hmrc.apiplatform.modules.applications.core.domain.models.DeleteRestriction +import uk.gov.hmrc.thirdpartyapplication.connector.{ApiPlatformEventsConnector, DisplayEvent} +import uk.gov.hmrc.thirdpartyapplication.models.db.StoredApplication +import uk.gov.hmrc.thirdpartyapplication.repository.ApplicationRepository + +class SetDeleteRestrictionJob @Inject() ( + setDeleteRestrictionJobLockService: SetDeleteRestrictionJobLockService, + applicationRepository: ApplicationRepository, + eventsConnector: ApiPlatformEventsConnector, + val clock: Clock, + jobConfig: SetDeleteRestrictionJobConfig + )(implicit val ec: ExecutionContext + ) extends ScheduledMongoJob + with ApplicationLogger + with ClockNow + with MongoJavatimeFormats.Implicits { + + override def name: String = "SetDeleteRestrictionJob" + override def isEnabled: Boolean = jobConfig.enabled + override def initialDelay: FiniteDuration = 1.minutes + override def interval: FiniteDuration = 100.days + override val lockService: LockService = setDeleteRestrictionJobLockService + implicit val hc: HeaderCarrier = HeaderCarrier() + + override def runJob(implicit ec: ExecutionContext): Future[RunningOfJobSuccessful] = { + logger.info("Running SetDeleteRestrictionJob") + + val result: Future[RunningOfJobSuccessful.type] = for { + applications <- applicationRepository.fetchAll(includeDeleted = true) + _ = logger.info(s"Scheduled job $name found ${applications.size} applications") + _ <- Future.sequence(applications.map(setDeleteRestriction(_))) + } yield RunningOfJobSuccessful + + result.recoverWith { + case e: Throwable => Future.failed(RunningOfJobFailed(name, e)) + } + } + + private def getDoNotDelete(event: Option[DisplayEvent]): DeleteRestriction = { + event match { + case None => DeleteRestriction.DoNotDelete("Set by process - Reason not found", Actors.ScheduledJob(name), instant()) + case Some(evt) => DeleteRestriction.DoNotDelete(evt.metaData.mkString, evt.actor, evt.eventDateTime) + } + } + + private def getDeleteRestriction(applicationId: ApplicationId, allowAutoDelete: Boolean): Future[DeleteRestriction] = { + if (allowAutoDelete) { + Future.successful(DeleteRestriction.NoRestriction) + } else { + eventsConnector.query(applicationId, Some("APP_LIFECYCLE"), None) + .map(events => events.find(e => e.eventType == "Application auto delete blocked")) + .map(getDoNotDelete(_)) + } + } + + private def setDeleteRestriction(app: StoredApplication) = { + for { + deleteRestriction <- getDeleteRestriction(app.id, app.allowAutoDelete) + savedApp <- applicationRepository.updateApplication(app.id, Updates.set("deleteRestriction", Codecs.toBson(deleteRestriction))) + _ = logger.info(s"[SetDeleteRestrictionJob]: Set deleteRestriction of application [${app.name} (${app.id})] to [$deleteRestriction]") + } yield savedApp + } +} + +class SetDeleteRestrictionJobLockService @Inject() (repository: LockRepository) + extends LockService { + + override val lockId: String = "SetDeleteRestriction" + override val lockRepository: LockRepository = repository + override val ttl: Duration = 1.hours +} + +case class SetDeleteRestrictionJobConfig(enabled: Boolean) diff --git a/conf/application.conf b/conf/application.conf index 2e5f3fb3a..5144a9979 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -220,6 +220,12 @@ termsOfUseInvitationOverdueJob { enabled = false } +setDeleteRestrictionJob { + initialDelay = 10m + interval = 100d + enabled = false +} + metricsJob { enabled = false } diff --git a/it/test/uk/gov/hmrc/thirdpartyapplication/repository/ApplicationRepositoryISpec.scala b/it/test/uk/gov/hmrc/thirdpartyapplication/repository/ApplicationRepositoryISpec.scala index d15bb01a8..2685d5c20 100644 --- a/it/test/uk/gov/hmrc/thirdpartyapplication/repository/ApplicationRepositoryISpec.scala +++ b/it/test/uk/gov/hmrc/thirdpartyapplication/repository/ApplicationRepositoryISpec.scala @@ -172,7 +172,8 @@ object ApplicationRepositoryISpecExample extends ServerBaseISpec with FixedClock ), "blocked" -> JsFalse, "ipAllowlist" -> Json.obj("required" -> JsFalse, "allowlist" -> JsArray.empty), - "allowAutoDelete" -> JsTrue + "allowAutoDelete" -> JsTrue, + "deleteRestriction" -> Json.obj("deleteRestrictionType" -> JsString("NO_RESTRICTION")) ) } @@ -1326,25 +1327,40 @@ class ApplicationRepositoryISpec } "fetchAll" should { + val application1 = anApplicationDataForTest( + id = ApplicationId.random, + prodClientId = generateClientId + ) + val application2 = anApplicationDataForTest( + id = ApplicationId.random, + prodClientId = generateClientId + ) + val application3 = createAppWithStatusUpdatedOn( + state = State.DELETED + ) - "fetch all existing applications" in { - val application1 = anApplicationDataForTest( - id = ApplicationId.random, - prodClientId = generateClientId - ) - val application2 = anApplicationDataForTest( - id = ApplicationId.random, - prodClientId = generateClientId - ) - + "fetch all existing applications - except DELETED apps" in { await(applicationRepository.save(application1)) await(applicationRepository.save(application2)) + await(applicationRepository.save(application3)) await(applicationRepository.fetchAll()) mustBe List( application1, application2 ) } + + "fetch all existing applications - including DELETED apps" in { + await(applicationRepository.save(application1)) + await(applicationRepository.save(application2)) + await(applicationRepository.save(application3)) + + await(applicationRepository.fetchAll(includeDeleted = true)) mustBe List( + application1, + application2, + application3 + ) + } } "fetchAllForContext" should { diff --git a/project/AppDependencies.scala b/project/AppDependencies.scala index 7a46da874..1b8164745 100644 --- a/project/AppDependencies.scala +++ b/project/AppDependencies.scala @@ -6,8 +6,8 @@ object AppDependencies { lazy val bootstrapVersion = "9.2.0" lazy val hmrcMongoVersion = "1.7.0" - lazy val applicationEventVersion = "0.68.0" - lazy val applicationDomainVersion = "0.64.0" + lazy val applicationEventVersion = "0.69.0" + lazy val applicationDomainVersion = "0.65.0" private lazy val compileDeps = Seq( "uk.gov.hmrc" %% "bootstrap-backend-play-30" % bootstrapVersion, diff --git a/test/uk/gov/hmrc/thirdpartyapplication/scheduled/SetDeleteRestrictionJobSpec.scala b/test/uk/gov/hmrc/thirdpartyapplication/scheduled/SetDeleteRestrictionJobSpec.scala new file mode 100644 index 000000000..10936dd07 --- /dev/null +++ b/test/uk/gov/hmrc/thirdpartyapplication/scheduled/SetDeleteRestrictionJobSpec.scala @@ -0,0 +1,118 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.thirdpartyapplication.scheduled + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.{Duration, DurationInt} +import scala.concurrent.{ExecutionContext, Future} + +import org.apache.pekko.stream.Materializer +import org.apache.pekko.stream.testkit.NoMaterializer + +import uk.gov.hmrc.mongo.lock.MongoLockRepository +import uk.gov.hmrc.mongo.test.{CleanMongoCollectionSupport, MongoSupport} +import uk.gov.hmrc.play.bootstrap.metrics.Metrics + +import uk.gov.hmrc.apiplatform.modules.common.domain.models.{Actors, ApplicationId, ClientId} +import uk.gov.hmrc.apiplatform.modules.common.utils.FixedClock +import uk.gov.hmrc.apiplatform.modules.applications.core.domain.models.{ApplicationStateFixtures, DeleteRestriction} +import uk.gov.hmrc.thirdpartyapplication.connector.{ApiPlatformEventsConnector, DisplayEvent} +import uk.gov.hmrc.thirdpartyapplication.models.db.{ApplicationTokens, StoredApplication, StoredToken} +import uk.gov.hmrc.thirdpartyapplication.repository.ApplicationRepository +import uk.gov.hmrc.thirdpartyapplication.util._ + +class SetDeleteRestrictionJobSpec + extends AsyncHmrcSpec + with MongoSupport + with CleanMongoCollectionSupport + with ApplicationStateFixtures + with NoMetricsGuiceOneAppPerSuite + with StoredApplicationFixtures + with CollaboratorTestData + with FixedClock { + + implicit lazy val materializer: Materializer = NoMaterializer + implicit val metrics: Metrics = app.injector.instanceOf[Metrics] + + val applicationRepository = new ApplicationRepository(mongoComponent, metrics) + val mockEventsConnector = mock[ApiPlatformEventsConnector] + + override protected def beforeEach(): Unit = { + super.beforeEach() + await(applicationRepository.collection.drop().toFuture()) + await(applicationRepository.ensureIndexes()) + } + + trait Setup { + val lockKeeperSuccess: () => Boolean = () => true + val mongoLockRepository: MongoLockRepository = app.injector.instanceOf[MongoLockRepository] + + val mockSetDeleteRestrictionJobLockService: SetDeleteRestrictionJobLockService = + new SetDeleteRestrictionJobLockService(mongoLockRepository) { + override val ttl: Duration = 1.minutes + + override def withLock[T](body: => Future[T])(implicit ec: ExecutionContext): Future[Option[T]] = + if (lockKeeperSuccess()) body.map(value => Some(value))(ec) else Future.successful(None) + } + + val jobConfig: SetDeleteRestrictionJobConfig = SetDeleteRestrictionJobConfig(true) + val underTest = new SetDeleteRestrictionJob(mockSetDeleteRestrictionJobLockService, applicationRepository, mockEventsConnector, clock, jobConfig) + } + + "SetDeleteRestrictionJob" should { + "update deleteRestriction fields in database" in new Setup { + + val appId1 = ApplicationId.random + val appId2 = ApplicationId.random + val appId3 = ApplicationId.random + + val bulkInsert = List( + testApplicationData(appId1, true), + testApplicationData(appId2, false), + testApplicationData(appId3, false) + ) + + await(Future.sequence(bulkInsert.map(i => applicationRepository.save(i)))) + + val reason = "reason" + val actor = Actors.GatekeeperUser("bob") + val event = DisplayEvent(appId2, instant, actor, "eventTagDescription", "Application auto delete blocked", List(reason)) + when(mockEventsConnector.query(eqTo(appId2), eqTo(Some("APP_LIFECYCLE")), eqTo(None))(*)).thenReturn(Future.successful(List(event))) + when(mockEventsConnector.query(eqTo(appId3), eqTo(Some("APP_LIFECYCLE")), eqTo(None))(*)).thenReturn(Future.successful(List.empty)) + + await(underTest.runJob) + + val actualApp1: StoredApplication = await(applicationRepository.fetch(appId1)).get + actualApp1.deleteRestriction shouldBe DeleteRestriction.NoRestriction + val actualApp2: StoredApplication = await(applicationRepository.fetch(appId2)).get + actualApp2.deleteRestriction shouldBe DeleteRestriction.DoNotDelete(reason, actor, instant) + val actualApp3: StoredApplication = await(applicationRepository.fetch(appId3)).get + actualApp3.deleteRestriction shouldBe DeleteRestriction.DoNotDelete("Set by process - Reason not found", Actors.ScheduledJob("SetDeleteRestrictionJob"), instant) + } + } + + def testApplicationData( + id: ApplicationId, + autoDelete: Boolean + ): StoredApplication = + storedApp + .withId(id) + .copy( + tokens = ApplicationTokens(StoredToken(ClientId.random, "ccc")), + allowAutoDelete = autoDelete + ) +}