Skip to content

Commit

Permalink
APIS-7389 Add new search filter for delete restriction (#535)
Browse files Browse the repository at this point in the history
* APIS-7389 Add new search filter for delete restriction

* APIS-7389 Update ProductionCredentialsRequestExpiredJob and ProductionCredentialsRequestExpiryWarningJob to use deleteRestriction
  • Loading branch information
petekirby-ee authored Jan 15, 2025
1 parent a80ae9d commit b7c3e3b
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ object ApplicationSearch {
case "accessType" => AccessTypeFilter(value.head)
case "lastUseBefore" | "lastUseAfter" => LastUseDateFilter(key, value.head)
case "allowAutoDelete" => AllowAutoDeleteFilter(value.head)
case "deleteRestriction" => DeleteRestrictionFilter(value.head)
case _ => None // ignore anything that isn't a search filter
}
}
Expand Down Expand Up @@ -173,6 +174,23 @@ case object AllowAutoDeleteFilter extends AllowAutoDeleteFilter {
}
}

sealed trait DeleteRestrictionFilter extends ApplicationSearchFilter

case object NoRestriction extends DeleteRestrictionFilter

case object DoNotDelete extends DeleteRestrictionFilter

case object DeleteRestrictionFilter extends DeleteRestrictionFilter {

def apply(value: String): Option[DeleteRestrictionFilter] = {
value match {
case "DO_NOT_DELETE" => Some(DoNotDelete)
case "NO_RESTRICTION" => Some(NoRestriction)
case _ => None
}
}
}

sealed trait LastUseDateFilter extends ApplicationSearchFilter

case class LastUseBeforeDate(lastUseDate: Instant) extends LastUseDateFilter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ class ApplicationRepository @Inject() (mongo: MongoComponent, val metrics: Metri
Seq(
filter(equal("state.name", state.toString())),
filter(equal("environment", Codecs.toBson(environment))),
filter(notEqual("allowAutoDelete", false)),
filter(notEqual("deleteRestriction.deleteRestrictionType", DeleteRestrictionType.DO_NOT_DELETE.toString())),
filter(lte("state.updatedOn", updatedBefore))
)
).toFuture()
Expand All @@ -437,7 +437,7 @@ class ApplicationRepository @Inject() (mongo: MongoComponent, val metrics: Metri
Seq(
filter(equal("state.name", state.toString())),
filter(equal("environment", Codecs.toBson(environment))),
filter(notEqual("allowAutoDelete", false)),
filter(notEqual("deleteRestriction.deleteRestrictionType", DeleteRestrictionType.DO_NOT_DELETE.toString())),
filter(lte("state.updatedOn", updatedBefore)),
lookup(from = "notifications", localField = "id", foreignField = "applicationId", as = "matched"),
filter(size("matched", 0))
Expand Down Expand Up @@ -611,7 +611,14 @@ class ApplicationRepository @Inject() (mongo: MongoComponent, val metrics: Metri
case false => matches(equal("allowAutoDelete", Codecs.toBson(allowAutoDelete)))
case true => matches(or(equal("allowAutoDelete", Codecs.toBson(allowAutoDelete)), exists("allowAutoDelete", false)))
}
}

def deleteRestrictionMatch(restrictionType: DeleteRestrictionType): Bson = {
restrictionType match {
case DeleteRestrictionType.DO_NOT_DELETE => matches(equal("deleteRestriction.deleteRestrictionType", Codecs.toBson(restrictionType)))
case DeleteRestrictionType.NO_RESTRICTION =>
matches(or(equal("deleteRestriction.deleteRestrictionType", Codecs.toBson(restrictionType)), exists("deleteRestriction.deleteRestrictionType", false)))
}
}

def specificAPISubscription(apiContext: ApiContext, apiVersion: Option[ApiVersionNbr]) = {
Expand Down Expand Up @@ -659,7 +666,12 @@ class ApplicationRepository @Inject() (mongo: MongoComponent, val metrics: Metri
// Allow Auto Delete
case AutoDeleteAllowed => allowAutoDeleteMatch(true)
case AutoDeleteBlocked => allowAutoDeleteMatch(false)
case _ => Document() // Only here to complete the match

// Delete Restriction
case NoRestriction => deleteRestrictionMatch(DeleteRestrictionType.NO_RESTRICTION)
case DoNotDelete => deleteRestrictionMatch(DeleteRestrictionType.DO_NOT_DELETE)

case _ => Document() // Only here to complete the match
}
}
// scalastyle:on cyclomatic.complexity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class ProductionCredentialsRequestExpiredJob @Inject() (

override def runJob(implicit ec: ExecutionContext): Future[RunningOfJobSuccessful] = {
val deleteTime: Instant = instant().minus(Period.ofDays(productionCredentialsRequestDeleteInterval.toDays.toInt))
logger.info(s"Delete expired production credentials requests for production applications having status of TESTING with updatedOn earlier than $deleteTime with allowAutoDelete true")
logger.info(s"Delete expired production credentials requests for production applications having status of TESTING with updatedOn earlier than $deleteTime with no delete restriction")

val result: Future[RunningOfJobSuccessful.type] = for {
deleteApps <-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ProductionCredentialsRequestExpiryWarningJob @Inject() (

override def runJob(implicit ec: ExecutionContext): Future[RunningOfJobSuccessful] = {
val warningTime: Instant = instant().minus(Period.ofDays(productionCredentialsRequestExpiryWarningInterval.toDays.toInt))
logger.info(s"Send production credentials request expiry warning email for production applications having status of TESTING with updatedOn earlier than $warningTime with allowAutoDelete true")
logger.info(s"Send production credentials request expiry warning email for production applications having status of TESTING with updatedOn earlier than $warningTime with no delete restriction")

val result: Future[RunningOfJobSuccessful.type] = for {
warningApps <-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class DeleteUnusedApplicationCommandHandler @Inject() (
def base64Decode(stringToDecode: String): Try[String] = Try(new String(Base64.getDecoder.decode(stringToDecode), StandardCharsets.UTF_8))

def matchesAuthorisationKey(cmd: DeleteUnusedApplication) =
cond(base64Decode(cmd.authorisationKey).map(_ == authControlConfig.authorisationKey).getOrElse(false), "Cannot delete this applicaton")
cond(base64Decode(cmd.authorisationKey).map(_ == authControlConfig.authorisationKey).getOrElse(false), "Cannot delete this application")

private def validate(app: StoredApplication, cmd: DeleteUnusedApplication): Validated[Failures, StoredApplication] = {
Apply[Validated[Failures, *]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ class ApplicationRepositoryISpec

retrieved mustBe application
retrieved.allowAutoDelete mustBe true
retrieved.deleteRestriction mustBe DeleteRestriction.NoRestriction
}

"update an application" in {
Expand Down Expand Up @@ -1009,6 +1010,7 @@ class ApplicationRepositoryISpec
val yesterday = currentDate.minus(Duration.ofDays(1))
val dayBeforeYesterday = currentDate.minus(Duration.ofDays(2))
val lastWeek = currentDate.minus(Duration.ofDays(7))
val doNotDelete = DeleteRestriction.DoNotDelete("reason", Actors.GatekeeperUser("gkuser"), instant)

def verifyApplications(
responseApplications: Seq[StoredApplication],
Expand All @@ -1028,7 +1030,7 @@ class ApplicationRepositoryISpec
createAppWithStatusUpdatedOn(State.TESTING, currentDate),
createAppWithStatusUpdatedOn(State.PENDING_REQUESTER_VERIFICATION, dayBeforeYesterday),
createAppWithStatusUpdatedOn(State.TESTING, dayBeforeYesterday),
createAppWithStatusUpdatedOn(State.TESTING, lastWeek).copy(allowAutoDelete = false),
createAppWithStatusUpdatedOn(State.TESTING, lastWeek).copy(deleteRestriction = doNotDelete),
createAppWithStatusUpdatedOn(State.TESTING, lastWeek)
)
applications.foreach(application =>
Expand Down Expand Up @@ -1057,6 +1059,7 @@ class ApplicationRepositoryISpec
val yesterday = currentDate.minus(Duration.ofDays(1))
val dayBeforeYesterday = currentDate.minus(Duration.ofDays(2))
val lastWeek = currentDate.minus(Duration.ofDays(7))
val doNotDelete = DeleteRestriction.DoNotDelete("reason", Actors.GatekeeperUser("gkuser"), instant)

def verifyApplications(
responseApplications: Seq[StoredApplication],
Expand All @@ -1076,7 +1079,7 @@ class ApplicationRepositoryISpec
createAppWithStatusUpdatedOn(State.TESTING, currentDate),
createAppWithStatusUpdatedOn(State.PENDING_REQUESTER_VERIFICATION, dayBeforeYesterday),
createAppWithStatusUpdatedOn(State.TESTING, dayBeforeYesterday),
createAppWithStatusUpdatedOn(State.TESTING, dayBeforeYesterday).copy(allowAutoDelete = false),
createAppWithStatusUpdatedOn(State.TESTING, dayBeforeYesterday).copy(deleteRestriction = doNotDelete),
createAppWithStatusUpdatedOn(State.TESTING, lastWeek)
)
applications.foreach(application =>
Expand Down Expand Up @@ -1104,7 +1107,7 @@ class ApplicationRepositoryISpec
val applications = Seq(
createAppWithStatusUpdatedOn(State.TESTING, currentDate),
createAppWithStatusUpdatedOn(State.PENDING_REQUESTER_VERIFICATION, dayBeforeYesterday),
createAppWithStatusUpdatedOn(State.TESTING, dayBeforeYesterday).copy(allowAutoDelete = false),
createAppWithStatusUpdatedOn(State.TESTING, dayBeforeYesterday).copy(deleteRestriction = doNotDelete),
createAppWithStatusUpdatedOn(State.TESTING, dayBeforeYesterday),
app4
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,64 @@ class ApplicationRepositorySearchISpec
result.applications.head.allowAutoDelete mustBe true
}

"return application with no delete restriction" in {
val applicationNoRestriction = anApplicationDataForTest(
id = ApplicationId.random,
prodClientId = generateClientId
)

val doNotDelete = DeleteRestriction.DoNotDelete("reason", Actors.GatekeeperUser("gkuser"), instant)
val applicationDoNotDelete = anApplicationDataForTest(
id = ApplicationId.random,
prodClientId = generateClientId
).copy(deleteRestriction = doNotDelete)

await(applicationRepository.save(applicationNoRestriction))
await(applicationRepository.save(applicationDoNotDelete))

val applicationSearch = new ApplicationSearch(filters = List(NoRestriction))

val result =
await(applicationRepository.searchApplications("testing")(applicationSearch))

result.totals.size mustBe 1
result.totals.head.total mustBe 2
result.matching.size mustBe 1
result.matching.head.total mustBe 1
result.applications.size mustBe 1
result.applications.head.id mustBe applicationNoRestriction.id
result.applications.head.deleteRestriction mustBe DeleteRestriction.NoRestriction
}

"return application with do not delete restriction" in {
val applicationNoRestriction = anApplicationDataForTest(
id = ApplicationId.random,
prodClientId = generateClientId
)

val doNotDelete = DeleteRestriction.DoNotDelete("reason", Actors.GatekeeperUser("gkuser"), instant)
val applicationDoNotDelete = anApplicationDataForTest(
id = ApplicationId.random,
prodClientId = generateClientId
).copy(deleteRestriction = doNotDelete)

await(applicationRepository.save(applicationNoRestriction))
await(applicationRepository.save(applicationDoNotDelete))

val applicationSearch = new ApplicationSearch(filters = List(DoNotDelete))

val result =
await(applicationRepository.searchApplications("testing")(applicationSearch))

result.totals.size mustBe 1
result.totals.head.total mustBe 2
result.matching.size mustBe 1
result.matching.head.total mustBe 1
result.applications.size mustBe 1
result.applications.head.id mustBe applicationDoNotDelete.id
result.applications.head.deleteRestriction mustBe doNotDelete
}

"return applications based on application state filter Active" in {
val applicationInTest = anApplicationDataForTest(
id = ApplicationId.random,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class DeleteUnusedApplicationCommandHandlerSpec extends CommandHandlerBaseSpec {
"return an error when auth key doesnt match" in new Setup {
val cmd = DeleteUnusedApplication("DeleteUnusedApplicationsJob", "notAuthKey", reasons, instant)

checkFailsWith("Cannot delete this applicaton") {
checkFailsWith("Cannot delete this application") {
underTest.process(app, cmd)
}
}
Expand Down

0 comments on commit b7c3e3b

Please sign in to comment.