Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PRC-500: Reconciliation endpoint for sync checks #15

Merged
merged 2 commits into from
Feb 28, 2025
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
@@ -1,5 +1,6 @@
package uk.gov.justice.digital.hmpps.organisationsapi.facade

import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateAddressPhoneRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateAddressRequest
Expand Down Expand Up @@ -90,6 +91,8 @@ class SyncFacade(
)
}

fun getIds(pageable: Pageable) = syncOrganisationService.getOrganisationIds(pageable)

// ================================================================
// Organisation phone numbers
// ================================================================
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package uk.gov.justice.digital.hmpps.organisationsapi.mapping.sync

import org.springframework.data.domain.Page
import uk.gov.justice.digital.hmpps.organisationsapi.entity.OrganisationWithFixedIdEntity
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationId
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationResponse

fun OrganisationWithFixedIdEntity.toModel(): SyncOrganisationResponse = SyncOrganisationResponse(
Expand Down Expand Up @@ -35,3 +37,7 @@ fun SyncCreateOrganisationRequest.toEntity() = OrganisationWithFixedIdEntity(
updatedBy = this.updatedBy,
updatedTime = this.updatedTime,
)

fun OrganisationWithFixedIdEntity.toModelIds(): SyncOrganisationId = SyncOrganisationId(organisationId = this.organisationId)

fun Page<OrganisationWithFixedIdEntity>.toModelIds(): Page<SyncOrganisationId> = map { it.toModelIds() }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync

import io.swagger.v3.oas.annotations.media.Schema

@Schema(description = "Response object for sync reconciliation")
data class SyncOrganisationId(
@Schema(description = "The ID for an organisation", example = "111111")
val organisationId: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
import org.springdoc.core.annotations.ParameterObject
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort.Direction
import org.springframework.data.web.PageableDefault
import org.springframework.http.MediaType
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.DeleteMapping
Expand All @@ -23,6 +28,7 @@ import org.springframework.web.bind.annotation.RestController
import uk.gov.justice.digital.hmpps.organisationsapi.facade.SyncFacade
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncUpdateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationId
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationResponse
import uk.gov.justice.digital.hmpps.organisationsapi.swagger.AuthApiResponses
import uk.gov.justice.hmpps.kotlin.common.ErrorResponse
Expand Down Expand Up @@ -174,4 +180,25 @@ class SyncOrganisationController(val syncFacade: SyncFacade) {
@PathVariable organisationId: Long,
@Valid @RequestBody updateOrganisationRequest: SyncUpdateOrganisationRequest,
) = syncFacade.updateOrganisation(organisationId, updateOrganisationRequest)

@GetMapping("/organisations/reconcile")
@Operation(
summary = "Reconciliation endpoint",
description = "Get a paged list of existing organisation IDs to reconcile against",
security = [SecurityRequirement(name = "bearer")],
)
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "Pageable organisation IDs returned",
),
],
)
@PreAuthorize("hasAnyRole('ROLE_ORGANISATIONS_MIGRATION')")
fun reconcileOrganisations(
@ParameterObject
@PageableDefault(sort = ["organisationId"], size = 100, direction = Direction.ASC)
pageable: Pageable,
): Page<SyncOrganisationId> = syncFacade.getIds(pageable)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package uk.gov.justice.digital.hmpps.organisationsapi.service.sync

import jakarta.persistence.EntityNotFoundException
import org.slf4j.LoggerFactory
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import uk.gov.justice.digital.hmpps.organisationsapi.mapping.sync.toEntity
import uk.gov.justice.digital.hmpps.organisationsapi.mapping.sync.toModel
import uk.gov.justice.digital.hmpps.organisationsapi.mapping.sync.toModelIds
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncUpdateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationId
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationResponse
import uk.gov.justice.digital.hmpps.organisationsapi.repository.OrganisationWithFixedIdRepository

Expand Down Expand Up @@ -78,4 +82,7 @@ class SyncOrganisationService(

return organisationRepository.saveAndFlush(changedOrganisation).toModel()
}

fun getOrganisationIds(pageable: Pageable): Page<SyncOrganisationId> =
organisationRepository.findAll(pageable).toModelIds()
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import uk.gov.justice.digital.hmpps.organisationsapi.model.response.migrate.Migr
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncAddressPhoneResponse
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncAddressResponse
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncEmailResponse
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationId
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationResponse
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncPhoneResponse
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncWebResponse
Expand Down Expand Up @@ -162,6 +163,17 @@ class TestAPIClient(private val webTestClient: WebTestClient, private val jwtAut
.expectBody(SyncWebResponse::class.java)
.returnResult().responseBody!!

fun syncReconcileOrganisations(page: Long = 0, size: Long = 10) = webTestClient.get()
.uri("/sync/organisations/reconcile?page=$page&size=$size")
.accept(MediaType.APPLICATION_JSON)
.headers(setAuthorisation(roles = listOf("ROLE_ORGANISATIONS_MIGRATION")))
.exchange()
.expectStatus()
.isOk
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody(OrganisationIdsResponse::class.java)
.returnResult().responseBody!!

fun getBadResponseErrors(uri: URI) = webTestClient.get()
.uri(uri.toString())
.accept(MediaType.APPLICATION_JSON)
Expand Down Expand Up @@ -225,4 +237,18 @@ class TestAPIClient(private val webTestClient: WebTestClient, private val jwtAut
val unsorted: Boolean,
val sorted: Boolean,
)

data class OrganisationIdsResponse(
val content: List<SyncOrganisationId>,
val pageable: ReturnedPageable,
val last: Boolean,
val totalPages: Int,
val totalElements: Int,
val first: Boolean,
val size: Int,
val number: Int,
val sort: ReturnedSort,
val numberOfElements: Int,
val empty: Boolean,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class OrganisationSearchIntegrationTest : SecureApiIntegrationTestBase() {
}

@ParameterizedTest
@ValueSource(strings = ["ROLE_ORGANISATIONS__R", "ROLE_ORGANISATONS__RW"])
@ValueSource(strings = ["ROLE_ORGANISATIONS__R", "ROLE_ORGANISATIONS__RW"])
fun `should return empty list if no organisation found and work with all roles`(role: String) {
val response = testAPIClient.searchOrganisations(OrganisationSearchRequest("ABC"))
assertThat(response.empty).isTrue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.test.context.TestPropertySource
import uk.gov.justice.digital.hmpps.organisationsapi.integration.PostgresIntegrationTestBase
import uk.gov.justice.digital.hmpps.organisationsapi.integration.helper.hasSize
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncUpdateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationResponse
Expand Down Expand Up @@ -232,6 +233,33 @@ class SyncOrganisationsIntegrationTest : PostgresIntegrationTestBase() {
stubEvents.assertHasNoEvents(OutboundEvent.ORGANISATION_CREATED)
}

@Test
fun `should support pageable organisation IDs for reconciliation`() {
testAPIClient.syncCreateAnOrganisation(syncCreateOrganisationRequest(5005L))
testAPIClient.syncCreateAnOrganisation(syncCreateOrganisationRequest(5006L))
testAPIClient.syncCreateAnOrganisation(syncCreateOrganisationRequest(5007L))

val firstPage = testAPIClient.syncReconcileOrganisations(0, 2)
with(firstPage) {
assertThat(totalElements).isGreaterThanOrEqualTo(3)
assertThat(content.hasSize(2))
assertThat(content).extracting("organisationId").hasSize(2)
}

val secondPage = testAPIClient.syncReconcileOrganisations(1, 2)
with(secondPage) {
assertThat(totalElements).isGreaterThanOrEqualTo(3)
assertThat(content.size).isGreaterThanOrEqualTo(1)
}

val bigPage = testAPIClient.syncReconcileOrganisations(0, 100)
with(bigPage) {
assertThat(totalElements).isGreaterThanOrEqualTo(3)
assertThat(content.size).isGreaterThanOrEqualTo(3)
assertThat(content).extracting("organisationId").containsAll(listOf(5005L, 5006L, 5007L))
}
}

private fun syncUpdateOrganisationRequest(organisationId: Long) = SyncUpdateOrganisationRequest(
organisationId = organisationId,
organisationName = "Organisation321",
Expand Down