Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.RetryApi
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.locationsinsideprison.model.Location
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.locationsinsideprison.model.ServiceUsingLocationDto.ServiceType
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.api.typeReference
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.model.LocationGroup
import java.util.*

@Service
Expand Down Expand Up @@ -58,4 +59,15 @@ class LocationsInsidePrisonAPIClient(
.bodyToMono(typeReference<List<Location>>())
.retryWhen(backoffSpec.withRetryContext(Context.of("api", "locations-inside-prison-api", "path", "/locations/prison/{prisonCode}/non-residential-usage-type/{usageType}")))
.awaitSingle()

suspend fun getLocationGroups(prisonCode: String): List<LocationGroup> = locationsInsidePrisonApiWebClient.get()
.uri { uriBuilder: UriBuilder ->
uriBuilder
.path("/locations/prison/{prisonCode}/groups")
.build(prisonCode)
}
.retrieve()
.bodyToMono(typeReference<List<LocationGroup>>())
.retryWhen(backoffSpec.withRetryContext(Context.of("api", "locations-inside-prison-api", "path", "/locations/prison/{prisonCode}/groups")))
.awaitSingle()
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.RetryApi
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.maybeQueryParam
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.model.InmateDetail
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.model.Location
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.model.LocationGroup
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.overrides.Education
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.overrides.LocationSummary
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.overrides.Movement
Expand Down Expand Up @@ -208,16 +207,6 @@ class PrisonApiClient(
.bodyToMono(typeReference<List<Location>>())
.retryWhen(backoffSpec.withRetryContext(Context.of("api", "prison-api", "path", "/api/agencies/{agencyId}/locations/type/{type}")))

fun getLocationGroups(agencyId: String): Mono<List<LocationGroup>> = prisonApiWebClient.get()
.uri { uriBuilder: UriBuilder ->
uriBuilder
.path("/api/agencies/{agencyId}/locations/groups")
.build(agencyId)
}
.retrieve()
.bodyToMono(typeReference<List<LocationGroup>>())
.retryWhen(backoffSpec.withRetryContext(Context.of("api", "prison-api", "path", "/api/agencies/{agencyId}/locations/groups")))

suspend fun getEventLocationsForPrison(prisonCode: String): PrisonLocations = getEventLocationsAsync(prisonCode).associateBy(Location::locationId)

fun getReferenceCode(domain: String, code: String): Mono<ReferenceCode> = prisonApiWebClient.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class LocationController(
@PreAuthorize("hasAnyRole('PRISON', 'ACTIVITY_ADMIN')")
fun getLocationGroups(
@PathVariable("prisonCode") prisonCode: String,
): List<LocationGroup>? = locationGroupServiceSelector.getLocationGroups(prisonCode)
): List<LocationGroup> = locationGroupServiceSelector.getLocationGroups(prisonCode)

@Deprecated("Use the /prison/{prisonCode}/location-prefixes endpoint below that return location prefixes for a specified locationKey and list of sub-locations")
@GetMapping(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.service

import kotlinx.coroutines.runBlocking
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.locationsinsideprison.api.LocationsInsidePrisonAPIClient
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.model.Location
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonapi.model.LocationGroup
import java.util.function.Predicate

@Service("defaultLocationGroupService")
class LocationGroupFromLocationsInsidePrisonApiService(private val locationsInsidePrisonApiClient: LocationsInsidePrisonAPIClient) : LocationGroupService {

override fun getLocationGroups(prisonCode: String): List<LocationGroup> = runBlocking { locationsInsidePrisonApiClient.getLocationGroups(prisonCode) }

override fun locationGroupFilter(prisonCode: String, groupName: String): Predicate<Location> {
val prefixToMatch = "$prisonCode-${groupName.replace('_', '-')}-"
return Predicate { it.locationPrefix?.startsWith(prefixToMatch) ?: false }
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,43 +18,43 @@ class LocationGroupFromPropertiesService(
) : LocationGroupService {

/**
* Return the set of Location Groups for an agency, including any nested sub-groups.
* Return the set of Location Groups for a prisonCode, including any nested sub-groups.
*
* @param agencyId The agency identifier
* @param prisonCode The prison identifier
* @return A list of LocationGroup, sorted by name, with each item containing its nested LocationGroups, also sorted by name.
*/
override fun getLocationGroups(agencyId: String): List<LocationGroup> {
override fun getLocationGroups(prisonCode: String): List<LocationGroup> {
val fullKeys = groupsProperties.stringPropertyNames()
return fullKeys.asSequence()
.filter { it.startsWith(agencyId) }
.map { it.substring(agencyId.length + 1) }
.filter { it.startsWith(prisonCode) }
.map { it.substring(prisonCode.length + 1) }
.filterNot { it.contains("_") }
.sorted()
.map { LocationGroup(it, it, getAvailableSubGroups(agencyId, it)) }
.map { LocationGroup(it, it, getAvailableSubGroups(prisonCode, it)) }
.toList()
}

/**
* Get the available sub-groups (sub-locations) for the named group/agency.
* Get the available sub-groups (sub-locations) for the named group/prisonCode.
*
* @param agencyId The agency identifier
* @param groupName The name of a group
* @param prisonCode The prison identifier
* @param groupName The name of a group
* @return Alphabetically sorted List of subgroups matching the criteria
*/
private fun getAvailableSubGroups(agencyId: String, groupName: String): List<LocationGroup> {
private fun getAvailableSubGroups(prisonCode: String, groupName: String): List<LocationGroup> {
val fullKeys = groupsProperties.stringPropertyNames()
val agencyAndGroupName = "${agencyId}_${groupName}_"
val prisonCodeAndGroupName = "${prisonCode}_${groupName}_"
return fullKeys.asSequence()
.filter { it.startsWith(agencyAndGroupName) }
.map { it.substring(agencyAndGroupName.length) }
.filter { it.startsWith(prisonCodeAndGroupName) }
.map { it.substring(prisonCodeAndGroupName.length) }
.sorted()
.map { LocationGroup(it, it, emptyList()) }
.toList()
}

override fun locationGroupFilter(agencyId: String, groupName: String): Predicate<Location> {
val patterns = groupsProperties.getProperty("${agencyId}_$groupName")
?: throw EntityNotFoundException("Group $groupName does not exist for agencyId $agencyId.")
override fun locationGroupFilter(prisonCode: String, groupName: String): Predicate<Location> {
val patterns = groupsProperties.getProperty("${prisonCode}_$groupName")
?: throw EntityNotFoundException("Group $groupName does not exist for prisonCode $prisonCode.")
val patternStrings = patterns.split(",")
return patternStrings.asSequence()
.map(Pattern::compile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.prisonap
import java.util.function.Predicate

interface LocationGroupService {
fun getLocationGroups(agencyId: String): List<LocationGroup>?
fun locationGroupFilter(agencyId: String, groupName: String): Predicate<Location>
fun getLocationGroups(prisonCode: String): List<LocationGroup>
fun locationGroupFilter(prisonCode: String, groupName: String): Predicate<Location>
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ class LocationGroupServiceSelector(
@Value("\${prison-locations.using-regex-config}") private val prisonsUsingRegexConfig: String,
) : LocationGroupService {

override fun getLocationGroups(agencyId: String): List<LocationGroup>? = if (isUsingRegexConfig(agencyId)) {
overrideService.getLocationGroups(agencyId)
override fun getLocationGroups(prisonCode: String): List<LocationGroup> = if (isUsingRegexConfig(prisonCode)) {
overrideService.getLocationGroups(prisonCode)
} else {
defaultService.getLocationGroups(agencyId)
defaultService.getLocationGroups(prisonCode)
}

override fun locationGroupFilter(agencyId: String, groupName: String): Predicate<Location> = if (isUsingRegexConfig(agencyId)) {
overrideService.locationGroupFilter(agencyId, groupName)
override fun locationGroupFilter(prisonCode: String, groupName: String): Predicate<Location> = if (isUsingRegexConfig(prisonCode)) {
overrideService.locationGroupFilter(prisonCode, groupName)
} else {
defaultService.locationGroupFilter(agencyId, groupName)
defaultService.locationGroupFilter(prisonCode, groupName)
}

private fun isUsingRegexConfig(agencyId: String): Boolean = prisonsUsingRegexConfig.split(",").contains(agencyId)
private fun isUsingRegexConfig(prisonCode: String): Boolean = prisonsUsingRegexConfig.split(",").contains(prisonCode)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.http.HttpStatus
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.WebClientRequestException
import org.springframework.web.reactive.function.client.WebClientResponseException
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.RetryApiService
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.client.locationsinsideprison.model.ServiceUsingLocationDto.ServiceType
import uk.gov.justice.digital.hmpps.hmppsactivitiesmanagementapi.helpers.dpsLocation
Expand Down Expand Up @@ -64,7 +66,7 @@ class LocationsInsidePrisonAPIClientTest {
}

@Test
fun `should return locations for for service type`() {
fun `should return locations for service type`() {
val mockLocations = mockServer.stubLocationsForServiceType()

runBlocking {
Expand Down Expand Up @@ -103,4 +105,39 @@ class LocationsInsidePrisonAPIClientTest {
}
}
}

@Test
fun `getLocationGroups - success`(): Unit = runBlocking {
val prisonCode = "MDI"
mockServer.stubGetLocationGroups(prisonCode, "locationsinsideprisonapi/location-groups-1.json")
val locationGroups = apiClient.getLocationGroups(prisonCode)

assertThat(locationGroups).hasSize(1)

locationGroups.first().apply {
assertThat(name).isEqualTo("Group Name")
assertThat(key).isEqualTo("Group key")
children.first().apply {
assertThat(name).isEqualTo("Child Group Name")
assertThat(key).isEqualTo("Child Group key")
assertThat(children).isEmpty()
}
}

assertThat(locationGroups.first().children).hasSize(1)
}

@Test
fun `getLocationGroups - not found`(): Unit = runBlocking {
val prisonCode = "LEI"
mockServer.stubGetLocationGroupsNotFound(prisonCode)

val exception = assertThrows<WebClientResponseException.NotFound> {
apiClient.getLocationGroups(prisonCode)
}

assertThat(exception.statusCode).isEqualTo(HttpStatus.NOT_FOUND)
assertThat(exception.message).contains("404 Not Found from GET http://localhost:8093/locations/prison/LEI/groups")
assertThat(exception.responseBodyAsString).contains("Location groups not found for prison")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -241,24 +241,6 @@ class PrisonApiClientTest {
.hasMessage("404 Not Found from GET http://localhost:8999/api/agencies/LEI/locations/type/CELL")
}

@Test
fun `getLocationGroups - success`() {
val agencyId = "MDI"
prisonApiMockServer.stubGetLocationGroups(agencyId, "prisonapi/location-groups-1.json")
val locationGroups = prisonApiClient.getLocationGroups(agencyId).block()!!
assertThat(locationGroups).hasSize(1)
assertThat(locationGroups.first().children.first().name).isEqualTo("Child Group Name")
}

@Test
fun `getLocationGroups - not found`() {
val agencyId = "LEI"
prisonApiMockServer.stubGetLocationGroupsNotFound(agencyId)
assertThatThrownBy { prisonApiClient.getLocationGroups(agencyId).block() }
.isInstanceOf(WebClientResponseException::class.java)
.hasMessage("404 Not Found from GET http://localhost:8999/api/agencies/LEI/locations/groups")
}

@Test
fun `getStudyArea - success`() {
prisonApiMockServer.stubGetReferenceCode("STUDY_AREA", "ENGLA", "prisonapi/study-area-code-ENGLA.json")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ class LocationIntegrationTest : IntegrationTestBase() {
}

@Test
fun `location groups - success - none in properties so should fetch from prison API`() {
val result = this::class.java.getResource("/__files/prisonapi/LEI_location_groups.json")?.readText()
fun `location groups - success - none in properties so should fetch from locations inside prison API`() {
val result = this::class.java.getResource("/__files/locationsinsideprisonapi/LEI_location_groups.json")?.readText()
val prisonCode = "LEI"

prisonApiMockServer.stubGetLocationGroups(prisonCode, "prisonapi/LEI_location_groups.json")
locationsInsidePrisonApiMockServer.stubGetLocationGroups(prisonCode, "locationsinsideprisonapi/LEI_location_groups.json")

webTestClient.get()
.uri { uriBuilder: UriBuilder ->
Expand All @@ -155,8 +155,6 @@ class LocationIntegrationTest : IntegrationTestBase() {
val result = this::class.java.getResource("/__files/prisonapi/location-groups-2.json")?.readText()
val prisonCode = "MDI"

prisonApiMockServer.stubGetLocationGroups(prisonCode, "prisonapi/location-groups-1.json")

webTestClient.get()
.uri { uriBuilder: UriBuilder ->
uriBuilder
Expand All @@ -174,7 +172,7 @@ class LocationIntegrationTest : IntegrationTestBase() {
fun `get location groups - not found`() {
val prisonCode = "XXX"

prisonApiMockServer.stubGetLocationGroupsNotFound(prisonCode)
locationsInsidePrisonApiMockServer.stubGetLocationGroupsNotFound(prisonCode)

val errorResponse = webTestClient.get()
.uri { uriBuilder: UriBuilder ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,28 @@ class LocationsInsidePrisonApiMockServer : MockServer(8093) {

return responseLocation
}

fun stubGetLocationGroups(prisonCode: String, jsonResponseFile: String) {
stubFor(
WireMock.get(WireMock.urlEqualTo("/locations/prison/$prisonCode/groups"))
.willReturn(
WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withBodyFile(jsonResponseFile)
.withStatus(200),
),
)
}

fun stubGetLocationGroupsNotFound(prisonCode: String) {
stubFor(
WireMock.get(WireMock.urlEqualTo("/locations/prison/$prisonCode/groups"))
.willReturn(
WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withBodyFile("locationsinsideprisonapi/location-group-404.json")
.withStatus(404),
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -297,30 +297,6 @@ class PrisonApiMockServer : MockServer(8999) {
)
}

fun stubGetLocationGroups(agencyId: String, jsonResponseFile: String) {
stubFor(
WireMock.get(WireMock.urlEqualTo("/api/agencies/$agencyId/locations/groups"))
.willReturn(
WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withBodyFile(jsonResponseFile)
.withStatus(200),
),
)
}

fun stubGetLocationGroupsNotFound(agencyId: String) {
stubFor(
WireMock.get(WireMock.urlEqualTo("/api/agencies/$agencyId/locations/groups"))
.willReturn(
WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withBodyFile("prisonapi/location-group-404.json")
.withStatus(404),
),
)
}

fun stubGetLocation(locationId: Long, jsonResponseFile: String, includeInactive: Boolean? = null) {
stubFor(
WireMock.get(WireMock.urlEqualTo("/api/locations/$locationId" + (includeInactive?.let { "?includeInactive=$includeInactive" } ?: "")))
Expand Down
Loading
Loading