Skip to content

Commit

Permalink
Merge pull request #2837 from ministryofjustice/feature/aps-1829-add-…
Browse files Browse the repository at this point in the history
…seed-job-to-create-test-bookings

APS-1829 - Enhance test app seed job to include bookings
  • Loading branch information
davidatkinsuk authored Jan 22, 2025
2 parents 5499765 + 0695490 commit 955a8f3
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas1

import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
import org.json.JSONObject
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
Expand All @@ -16,14 +19,18 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ServiceName
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.SituationOption
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.SubmitApprovedPremisesApplication
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesApplicationEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.AssessmentRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.BookingEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.BookingRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingEntity.Companion.CHARACTERISTICS_OF_INTEREST
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.CharacteristicEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.CharacteristicRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.OfflineApplicationEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.PlacementRequestRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.PostcodeDistrictRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.PersonInfoResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.problem.ForbiddenProblem
Expand All @@ -35,13 +42,16 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.EnvironmentServi
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.OffenderService
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.UserService
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.UserService.GetUserResponse
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.cas1.Cas1SpaceBookingService
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.ensureEntityFromCasResultIsSuccess
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.extractEntityFromCasResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.extractEntityFromNestedAuthorisableValidatableActionResult
import java.io.IOException
import java.time.LocalDate
import java.time.OffsetDateTime
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.random.Random

@SuppressWarnings("MagicNumber", "TooGenericExceptionCaught")
@Service
Expand All @@ -50,19 +60,42 @@ class Cas1ApplicationSeedService(
private val bookingRepository: BookingRepository,
private val applicationTimelineNoteService: ApplicationTimelineNoteService,
private val spaceBookingRepository: Cas1SpaceBookingRepository,
private val offenderService: OffenderService,
private val applicationService: ApplicationService,
private val userService: UserService,
private val environmentService: EnvironmentService,
private val assessmentService: AssessmentService,
private val assessmentRepository: AssessmentRepository,
private val postcodeDistrictRepository: PostcodeDistrictRepository,
private val cache: Cas1ApplicationSeedServiceCaches,
private val spaceBookingService: Cas1SpaceBookingService,
private val placementRequestRepository: PlacementRequestRepository,
private val premisesRepository: ApprovedPremisesRepository,
private val characteristicsRepository: CharacteristicRepository,
) {
private val log = LoggerFactory.getLogger(this::class.java)
companion object {
private val log = LoggerFactory.getLogger(this::class.java)

val ASSESSMENT_DATA_JSON = loadFixtureAsResource("assessment_data.json")
val ASSESSMENT_DOCUMENT_JSON = loadFixtureAsResource("assessment_document.json")
val APPLICATION_DATA_JSON = loadFixtureAsResource("application_data.json")
val APPLICATION_DOCUMENT_JSON: MutableMap<String, Any> = JSONObject(loadFixtureAsResource("application_document.json")).toMap()

private fun loadFixtureAsResource(filename: String): String {
val path = "db/seed/local+dev+test/cas1_application_data/$filename"

try {
return this::class.java.classLoader.getResource(path)?.readText() ?: ""
} catch (e: IOException) {
log.warn("Failed to load seed fixture $path: " + e.message!!)
return "{}"
}
}
}

enum class ApplicationState {
PENDING_SUBMISSION,
AUTHORISED,
BOOKED,
}

@SuppressWarnings("TooGenericExceptionCaught")
Expand All @@ -71,12 +104,13 @@ class Cas1ApplicationSeedService(
crn: String,
createIfExistingApplicationForCrn: Boolean = false,
state: ApplicationState,
premisesQCode: String? = null,
) {
if (environmentService.isNotATestEnvironment()) {
error("Cannot create test applications as not in a test environment")
}

createApplicationInternal(deliusUserName, crn, createIfExistingApplicationForCrn, state)
createApplicationInternal(deliusUserName, crn, createIfExistingApplicationForCrn, state, premisesQCode)
}

fun createOfflineApplicationWithBooking(deliusUserName: String, crn: String) {
Expand All @@ -92,6 +126,7 @@ class Cas1ApplicationSeedService(
crn: String,
createIfExistingApplicationForCrn: Boolean,
state: ApplicationState,
premisesQCode: String?,
) {
if (!createIfExistingApplicationForCrn && applicationService.getApplicationsForCrn(crn, ServiceName.approvedPremises).isNotEmpty()) {
log.info("Already have CAS1 application for $crn, not seeding a new application")
Expand All @@ -109,14 +144,24 @@ class Cas1ApplicationSeedService(
submitApplication(application)
assessAndAcceptApplication(application)
}
ApplicationState.BOOKED -> {
val application = createApplicationPendingSubmission(deliusUserName, crn)
submitApplication(application)
assessAndAcceptApplication(application)
val premises = premisesRepository.findByQCode(premisesQCode!!)!!
createSpaceBooking(
application,
premises,
)
}
}
}

private fun createApplicationPendingSubmission(
deliusUserName: String,
crn: String,
): ApprovedPremisesApplicationEntity {
val personInfo = getPersonInfo(crn)
val personInfo = cache.getPersonInfo(crn)
val createdByUser = (userService.getExistingUserOrCreate(deliusUserName) as GetUserResponse.Success).user

val newApplicationEntity = extractEntityFromCasResult(
Expand All @@ -141,7 +186,7 @@ class Cas1ApplicationSeedService(
apType = ApType.normal,
releaseType = "licence",
arrivalDate = LocalDate.of(2025, 12, 12),
data = loadFixtureAsResource("application_data.json"),
data = APPLICATION_DATA_JSON,
isInapplicable = false,
noticeType = Cas1ApplicationTimelinessCategory.standard,
),
Expand All @@ -167,7 +212,7 @@ class Cas1ApplicationSeedService(
applicationId = application.id,
submitApplication = SubmitApprovedPremisesApplication(
apType = ApType.normal,
translatedDocument = JSONObject(loadFixtureAsResource("application_document.json")).toMap(),
translatedDocument = APPLICATION_DOCUMENT_JSON,
caseManagerIsNotApplicant = false,
isWomensApplication = false,
releaseType = ReleaseTypeOption.licence,
Expand Down Expand Up @@ -204,15 +249,15 @@ class Cas1ApplicationSeedService(
assessmentService.updateAssessment(
assessmentId = getAssessmentId(application),
updatingUser = assessor,
data = loadFixtureAsResource("assessment_data.json"),
data = ASSESSMENT_DATA_JSON,
),
)

ensureEntityFromCasResultIsSuccess(
assessmentService.acceptAssessment(
acceptingUser = assessor,
assessmentId = getAssessmentId(application),
document = loadFixtureAsResource("assessment_document.json"),
document = ASSESSMENT_DOCUMENT_JSON,
placementRequirements = PlacementRequirements(
gender = Gender.male,
type = ApType.normal,
Expand All @@ -231,6 +276,27 @@ class Cas1ApplicationSeedService(
)
}

@SuppressWarnings("MagicNumber")
private fun createSpaceBooking(
application: ApprovedPremisesApplicationEntity,
premises: ApprovedPremisesEntity,
) {
val arrivalDate = LocalDate.now().minusDays(Random.nextLong(0, 7))
val departureDate = arrivalDate.plusDays(Random.nextLong(1, 365))
val characteristics = CHARACTERISTICS_OF_INTEREST.shuffled().take(Random.nextInt(0, CHARACTERISTICS_OF_INTEREST.size - 1))

ensureEntityFromCasResultIsSuccess(
spaceBookingService.createNewBooking(
premisesId = premises.id,
placementRequestId = placementRequestRepository.findByApplication(application).first().id,
arrivalDate = arrivalDate,
departureDate = departureDate,
createdBy = application.createdByUser,
characteristics = characteristicsRepository.findAllWherePropertyNameIn(characteristics, "approved-premises"),
),
)
}

private fun getAssessmentId(application: ApprovedPremisesApplicationEntity) = assessmentRepository.findByApplicationIdAndReallocatedAtNull(applicationId = application.id)!!.id

private fun createOfflineApplicationInternal(deliusUserName: String, crn: String) {
Expand All @@ -239,7 +305,7 @@ class Cas1ApplicationSeedService(
return
}

val personInfo = getPersonInfo(crn)
val personInfo = cache.getPersonInfo(crn)
val offenderDetail = personInfo.offenderDetailSummary

val offlineApplication = applicationService.createOfflineApplication(
Expand Down Expand Up @@ -327,32 +393,37 @@ class Cas1ApplicationSeedService(
),
)
}
}

private fun getPersonInfo(crn: String) =
when (
val personInfoResult = offenderService.getPersonInfoResult(
crn = crn,
deliusUsername = null,
ignoreLaoRestrictions = true,
)
) {
is PersonInfoResult.NotFound, is PersonInfoResult.Unknown -> throw NotFoundProblem(
personInfoResult.crn,
"Offender",
)

is PersonInfoResult.Success.Restricted -> throw ForbiddenProblem()
is PersonInfoResult.Success.Full -> personInfoResult
}
@SuppressWarnings("MagicNumber")
@Service
class Cas1ApplicationSeedServiceCaches(
private val offenderService: OffenderService,
) {

private fun loadFixtureAsResource(filename: String): String {
val path = "db/seed/local+dev+test/cas1_application_data/$filename"
var personInfoCache: LoadingCache<String, PersonInfoResult.Success.Full> = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(object : CacheLoader<String, PersonInfoResult.Success.Full>() {
override fun load(crn: String): PersonInfoResult.Success.Full {
return when (
val personInfoResult = offenderService.getPersonInfoResult(
crn = crn,
deliusUsername = null,
ignoreLaoRestrictions = true,
)
) {
is PersonInfoResult.NotFound, is PersonInfoResult.Unknown -> throw NotFoundProblem(
personInfoResult.crn,
"Offender",
)

is PersonInfoResult.Success.Restricted -> throw ForbiddenProblem()
is PersonInfoResult.Success.Full -> personInfoResult
}
}
})

try {
return this::class.java.classLoader.getResource(path)?.readText() ?: ""
} catch (e: IOException) {
log.warn("Failed to load seed fixture $path: " + e.message!!)
return "{}"
}
}
fun getPersonInfo(crn: String): PersonInfoResult.Success.Full =
personInfoCache.get(crn)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Cas1CreateTestApplicationsSeedJob(
"crn",
"count",
"state",
"premises_qcode",
),
runInTransaction = false,
) {
Expand All @@ -34,6 +35,7 @@ class Cas1CreateTestApplicationsSeedJob(
crn = seedColumns.getStringOrNull("crn")!!,
count = seedColumns.getIntOrNull("count")!!,
state = Cas1ApplicationSeedService.ApplicationState.valueOf(seedColumns.getStringOrNull("state")!!),
premisesQCode = seedColumns.getStringOrNull("premises_qcode"),
)
}

Expand All @@ -56,6 +58,7 @@ class Cas1CreateTestApplicationsSeedJob(
crn = crn,
createIfExistingApplicationForCrn = true,
state = row.state,
premisesQCode = row.premisesQCode,
)
}
}
Expand All @@ -71,4 +74,5 @@ data class Cas1CreateTestApplicationsSeedCsvRow(
val crn: String,
val count: Int,
val state: Cas1ApplicationSeedService.ApplicationState,
val premisesQCode: String?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.SeedFileType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.factory.NeedsDetailsFactory
import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.givens.givenAUser
import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.givens.givenAnApprovedPremises
import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.givens.givenAnOffender
import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.httpmocks.apDeliusContextMockSuccessfulTeamsManagingCaseCall
import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.httpmocks.apOASysContextMockSuccessfulNeedsDetailsCall
Expand All @@ -29,6 +30,8 @@ class SeedCas1CreateTestApplicationSeedTest : SeedTestBase() {
apOASysContextMockSuccessfulNeedsDetailsCall(crn, NeedsDetailsFactory().produce())
govUKBankHolidaysAPIMockSuccessfullCallWithEmptyResponse()

val premises = givenAnApprovedPremises(supportsSpaceBookings = true)

postCodeDistrictFactory.produceAndPersist()

withCsv(
Expand All @@ -38,7 +41,8 @@ class SeedCas1CreateTestApplicationSeedTest : SeedTestBase() {
creatorUsername = user.deliusUsername,
crn = crn,
count = 1,
state = Cas1ApplicationSeedService.ApplicationState.AUTHORISED,
state = Cas1ApplicationSeedService.ApplicationState.BOOKED,
premisesQCode = premises.qCode,
),
).toCsv(),
)
Expand All @@ -56,6 +60,7 @@ class SeedCas1CreateTestApplicationSeedTest : SeedTestBase() {
"crn",
"count",
"state",
"premises_qcode",
)
.newRow()

Expand All @@ -65,6 +70,7 @@ class SeedCas1CreateTestApplicationSeedTest : SeedTestBase() {
.withQuotedField(it.crn)
.withQuotedField(it.count)
.withQuotedField(it.state)
.withQuotedField(it.premisesQCode ?: "")
.newRow()
}

Expand Down

0 comments on commit 955a8f3

Please sign in to comment.