Skip to content

Commit

Permalink
Make record-arrival endpoint idempotent
Browse files Browse the repository at this point in the history
  • Loading branch information
gregkhawkins committed Nov 8, 2024
1 parent 5866d0d commit 1b9a879
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ interface DomainEventRepository : JpaRepository<DomainEventEntity, UUID> {
)
fun findIdsByTypeAndBookingId(type: DomainEventType, bookingId: UUID): List<UUID>

fun getByApplicationIdAndType(applicationId: UUID, type: DomainEventType): DomainEventEntity
fun findByApplicationIdAndType(applicationId: UUID, type: DomainEventType): List<DomainEventEntity>

fun findByAssessmentIdAndType(assessmentId: UUID, type: DomainEventType): List<DomainEventEntity>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,11 @@ class Cas1SpaceBookingService(
existingCas1SpaceBooking!!

if (existingCas1SpaceBooking.actualArrivalDateTime != null) {
return existingCas1SpaceBooking.id hasConflictError "An arrival is already recorded for this Space Booking"
return if (cas1NewArrival.arrivalDateTime == existingCas1SpaceBooking.actualArrivalDateTime) {
success(existingCas1SpaceBooking)
} else {
existingCas1SpaceBooking.id hasConflictError "An arrival is already recorded for this Space Booking"
}
}

existingCas1SpaceBooking.actualArrivalDateTime = cas1NewArrival.arrivalDateTime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ class DomainEventAsserter(
).hasSize(expectedCount)
}

fun assertHasZeroDomainEventsStoredOfEventType(applicationId: UUID, eventType: DomainEventType) =
assertDomainEventStoreCount(applicationId, eventType, expectedCount = 0)

private fun assertDomainEventStoreCount(applicationId: UUID, eventType: DomainEventType, expectedCount: Int) {
assertThat(
domainEventRepository.findByApplicationIdAndType(
applicationId = applicationId,
type = eventType,
),
).hasSize(expectedCount)
}

fun assertDomainEventOfTypeStored(applicationId: UUID, eventType: DomainEventType): DomainEventEntity {
assertThat(
domainEventRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.unit.transformer.cas1.Ca
import uk.gov.justice.digital.hmpps.approvedpremisesapi.unit.transformer.cas1.TestCaseForSpaceBookingSummaryStatus
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.bodyAsListOfObjects
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.isWithinTheLastMinute
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.toLocalDate
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
Expand Down Expand Up @@ -971,6 +972,93 @@ class Cas1SpaceBookingTest {
.isOk
domainEventAsserter.assertDomainEventOfTypeStored(spaceBooking.application!!.id, DomainEventType.APPROVED_PREMISES_PERSON_ARRIVED)
}

@Test
fun `Recording arrival returns OK when arrival already registered and arrival date matches`() {
val (_, jwt) = `Given a User`(roles = listOf(CAS1_FUTURE_MANAGER))

val (user) = `Given a User`()
val (offender) = `Given an Offender`()
val (placementRequest) = `Given a Placement Request`(
placementRequestAllocatedTo = user,
assessmentAllocatedTo = user,
createdByUser = user,
)

val arrivedYesterday = LocalDateTime.now().toInstant(ZoneOffset.UTC)

spaceBooking = cas1SpaceBookingEntityFactory.produceAndPersist {
withCrn(offender.otherIds.crn)
withPremises(premises)
withPlacementRequest(placementRequest)
withApplication(placementRequest.application)
withCreatedBy(user)
withCanonicalArrivalDate(arrivedYesterday.toLocalDate())
withCanonicalDepartureDate(LocalDate.now().plusYears(1))
withActualArrivalDateTime(arrivedYesterday)
withKeyworkerName(user.name)
withKeyworkerStaffCode(user.deliusStaffCode)
withKeyworkerAssignedAt(Instant.now())
withDeliusEventNumber("25")
}

webTestClient.post()
.uri("/cas1/premises/${premises.id}/space-bookings/${spaceBooking.id}/arrival")
.header("Authorization", "Bearer $jwt")
.bodyValue(
Cas1NewArrival(
arrivalDateTime = arrivedYesterday
),
)
.exchange()
.expectStatus()
.isOk
domainEventAsserter.assertHasZeroDomainEventsStoredOfEventType(spaceBooking.application!!.id, DomainEventType.APPROVED_PREMISES_PERSON_ARRIVED)
}

@Test
fun `Recording arrival returns 4xx error when arrival already registered and arrival date does not match`() {
val (_, jwt) = `Given a User`(roles = listOf(CAS1_FUTURE_MANAGER))

val (user) = `Given a User`()
val (offender) = `Given an Offender`()
val (placementRequest) = `Given a Placement Request`(
placementRequestAllocatedTo = user,
assessmentAllocatedTo = user,
createdByUser = user,
)

val arrivedYesterday = LocalDateTime.now().toInstant(ZoneOffset.UTC)
val arrivedToday = LocalDateTime.now().toInstant(ZoneOffset.UTC)

spaceBooking = cas1SpaceBookingEntityFactory.produceAndPersist {
withCrn(offender.otherIds.crn)
withPremises(premises)
withPlacementRequest(placementRequest)
withApplication(placementRequest.application)
withCreatedBy(user)
withCanonicalArrivalDate(arrivedYesterday.toLocalDate())
withCanonicalDepartureDate(LocalDate.now().plusYears(1))
withActualArrivalDateTime(arrivedYesterday)
withKeyworkerName(user.name)
withKeyworkerStaffCode(user.deliusStaffCode)
withKeyworkerAssignedAt(Instant.now())
withDeliusEventNumber("25")
}

webTestClient.post()
.uri("/cas1/premises/${premises.id}/space-bookings/${spaceBooking.id}/arrival")
.header("Authorization", "Bearer $jwt")
.bodyValue(
Cas1NewArrival(
arrivalDateTime = arrivedToday
),
)
.exchange()
.expectStatus()
.is4xxClientError
domainEventAsserter.assertHasZeroDomainEventsStoredOfEventType(spaceBooking.application!!.id, DomainEventType.APPROVED_PREMISES_PERSON_ARRIVED)
}
}

@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ class Cas1SpaceBookingServiceTest {
}

@Test
fun `Returns conflict error if the space booking record already has an arrival date recorded`() {
fun `Returns conflict error if the space booking record already has an arrival date recorded that does not match the received arrival date`() {
val existingSpaceBookingWithArrivalDate = existingSpaceBooking.copy(actualArrivalDateTime = originalArrivalDate)

every { cas1PremisesService.findPremiseById(any()) } returns premises
Expand All @@ -536,6 +536,39 @@ class Cas1SpaceBookingServiceTest {
assertThat(result.message).isEqualTo("An arrival is already recorded for this Space Booking")
}

@Test
fun `Returns success if the space booking record already has an arrival date recorded that matches the received arrival date`() {
val existingSpaceBookingWithArrivalDate = existingSpaceBooking.copy(
actualArrivalDateTime = originalArrivalDate,
canonicalArrivalDate = originalArrivalDate.toLocalDate()
)

every { cas1PremisesService.findPremiseById(any()) } returns premises
every { spaceBookingRepository.findByIdOrNull(any()) } returns existingSpaceBookingWithArrivalDate

val result = service.recordArrivalForBooking(
premisesId = UUID.randomUUID(),
bookingId = UUID.randomUUID(),
cas1NewArrival = Cas1NewArrival(originalArrivalDate),
)

assertThat(result).isInstanceOf(CasResult.Success::class.java)

val extractedResult = (result as CasResult.Success).value
assertThat(existingSpaceBookingWithArrivalDate.premises).isEqualTo(extractedResult.premises)
assertThat(existingSpaceBookingWithArrivalDate.placementRequest).isEqualTo(extractedResult.placementRequest)
assertThat(existingSpaceBookingWithArrivalDate.application).isEqualTo(extractedResult.application)
assertThat(existingSpaceBookingWithArrivalDate.createdAt).isEqualTo(extractedResult.createdAt)
assertThat(existingSpaceBookingWithArrivalDate.createdBy).isEqualTo(extractedResult.createdBy)
assertThat(existingSpaceBookingWithArrivalDate.actualDepartureDateTime).isEqualTo(extractedResult.actualDepartureDateTime)
assertThat(existingSpaceBookingWithArrivalDate.crn).isEqualTo(extractedResult.crn)
assertThat(existingSpaceBookingWithArrivalDate.keyWorkerStaffCode).isEqualTo(extractedResult.keyWorkerStaffCode)
assertThat(existingSpaceBookingWithArrivalDate.keyWorkerAssignedAt).isEqualTo(extractedResult.keyWorkerAssignedAt)
assertThat(existingSpaceBookingWithArrivalDate.expectedArrivalDate).isEqualTo(extractedResult.expectedArrivalDate)
assertThat(originalArrivalDate).isEqualTo(extractedResult.actualArrivalDateTime)
assertThat(originalArrivalDate.toLocalDate()).isEqualTo(extractedResult.canonicalArrivalDate)
}

@Test
fun `Updates existing space booking with arrival information and raises domain event`() {
val updatedSpaceBookingCaptor = slot<Cas1SpaceBookingEntity>()
Expand Down

0 comments on commit 1b9a879

Please sign in to comment.