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

PI-2670 - Common-platform-and-delius - Feature flag to toggle data creation #4490

Merged
merged 17 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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 @@ -18,8 +18,10 @@ import org.springframework.boot.test.mock.mockito.SpyBean
import uk.gov.justice.digital.hmpps.audit.entity.AuditedInteraction
import uk.gov.justice.digital.hmpps.audit.service.AuditedInteractionService
import uk.gov.justice.digital.hmpps.data.generator.MessageGenerator
import uk.gov.justice.digital.hmpps.flags.FeatureFlags
import uk.gov.justice.digital.hmpps.integrations.delius.audit.BusinessInteractionCode
import uk.gov.justice.digital.hmpps.integrations.delius.entity.Person
import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonManagerRepository
import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonRepository
import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonAddress
Expand Down Expand Up @@ -63,12 +65,19 @@ internal class IntegrationTest {
@SpyBean
lateinit var addressRepository: PersonAddressRepository

@SpyBean
lateinit var personManagerRepository: PersonManagerRepository

@SpyBean
lateinit var personService: PersonService

@MockBean
private lateinit var featureFlags: FeatureFlags

@BeforeEach
fun setup() {
doReturn("A111111").whenever(personService).generateCrn()
whenever(featureFlags.enabled("common-platform-record-creation-toggle")).thenReturn(true)
}

@Test
Expand Down Expand Up @@ -319,10 +328,21 @@ internal class IntegrationTest {
})
}

@Test
fun `When feature flag is disabled no records are inserted`() {
whenever(featureFlags.enabled("common-platform-record-creation-toggle")).thenReturn(false)
val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT)
channelManager.getChannel(queueName).publishAndWait(notification)
verify(personService, never()).insertPerson(any(), any())
thenNoRecordsAreInserted()
verify(auditedInteractionService, Mockito.never())
.createAuditedInteraction(any(), any(), eq(AuditedInteraction.Outcome.FAIL), any(), anyOrNull())
}

private fun thenNoRecordsAreInserted() {
verify(personService, never()).insertAddress(any())
verify(addressRepository, never()).save(any())
verify(personRepository, never()).save(any())
verify(personManagerRepository, never()).save(any())
verify(auditedInteractionService, Mockito.never())
.createAuditedInteraction(any(), any(), eq(AuditedInteraction.Outcome.SUCCESS), any(), anyOrNull())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import org.openfolder.kotlinasyncapi.annotation.channel.Message
import org.openfolder.kotlinasyncapi.annotation.channel.Publish
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.converter.NotificationConverter
import uk.gov.justice.digital.hmpps.flags.FeatureFlags
import uk.gov.justice.digital.hmpps.integrations.client.ProbationMatchRequest
import uk.gov.justice.digital.hmpps.integrations.client.ProbationSearchClient
import uk.gov.justice.digital.hmpps.message.Notification
import uk.gov.justice.digital.hmpps.service.PersonService
import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
import java.time.LocalDate
import java.time.Period

@Component
@Channel("common-platform-and-delius-queue")
Expand All @@ -20,7 +23,8 @@ class Handler(
private val notifier: Notifier,
private val telemetryService: TelemetryService,
private val personService: PersonService,
private val probationSearchClient: ProbationSearchClient
private val probationSearchClient: ProbationSearchClient,
private val featureFlags: FeatureFlags
) : NotificationHandler<CommonPlatformHearing> {

@Publish(messages = [Message(title = "COMMON_PLATFORM_HEARING", payload = Schema(CommonPlatformHearing::class))])
Expand All @@ -41,34 +45,62 @@ class Handler(
return
}

val courtCode = notification.message.hearing.courtCentre.code

defendants.forEach { defendant ->

val matchRequest = defendant.toProbationMatchRequest() ?: return@forEach

val matchedPersonResponse = probationSearchClient.match(matchRequest)

if (matchedPersonResponse.matches.isNotEmpty()) {
telemetryService.trackEvent(
"ProbationSearchMatchDetected",
mapOf(
"hearingId" to notification.message.hearing.id,
"defendantId" to defendant.id,
)
marcus-bcl marked this conversation as resolved.
Show resolved Hide resolved
)
return@forEach
}
marcus-bcl marked this conversation as resolved.
Show resolved Hide resolved

// Insert each defendant as a person record
val savedEntities = personService.insertPerson(defendant, courtCode)
val dateOfBirth = defendant.personDefendant?.personDetails?.dateOfBirth
?: throw IllegalArgumentException("Date of birth not found in message")

notifier.caseCreated(savedEntities.person)
savedEntities.address?.let { notifier.addressCreated(it) }
// Under 10 years old validation
dateOfBirth.let {
val age = Period.between(it, LocalDate.now()).years
require(age > 10) {
"Date of birth would indicate person is under ten years old: $it"
}
}

telemetryService.trackEvent(
"PersonCreated", mapOf(
"hearingId" to notification.message.hearing.id,
"CRN" to savedEntities.person.crn,
"personId" to savedEntities.person.id.toString(),
"personManagerId" to savedEntities.personManager.id.toString(),
"equalityId" to savedEntities.equality.id.toString(),
"addressId" to savedEntities.address?.id.toString()
if (featureFlags.enabled("common-platform-record-creation-toggle")) {
// Insert each defendant as a person record
val savedEntities = personService.insertPerson(defendant, notification.message.hearing.courtCentre.code)

notifier.caseCreated(savedEntities.person)
savedEntities.address?.let { notifier.addressCreated(it) }

telemetryService.trackEvent(
"PersonCreated",
mapOf(
"hearingId" to notification.message.hearing.id,
"defendantId" to defendant.id,
"CRN" to savedEntities.person.crn,
"personId" to savedEntities.person.id.toString(),
"personManagerId" to savedEntities.personManager.id.toString(),
"equalityId" to savedEntities.equality.id.toString(),
"addressId" to savedEntities.address?.id.toString(),
)
)
} else {
telemetryService.trackEvent(
"SimulatedPersonCreated",
mapOf(
"hearingId" to notification.message.hearing.id,
"defendantId" to defendant.id
)
)
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package uk.gov.justice.digital.hmpps.service

import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import uk.gov.justice.digital.hmpps.audit.service.AuditableService
import uk.gov.justice.digital.hmpps.audit.service.AuditedInteractionService
import uk.gov.justice.digital.hmpps.integrations.client.OsClient
Expand All @@ -14,7 +13,6 @@ import uk.gov.justice.digital.hmpps.messaging.Address
import uk.gov.justice.digital.hmpps.messaging.Defendant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.Period

@Service
class PersonService(
Expand All @@ -30,21 +28,8 @@ class PersonService(
private val osClient: OsClient
) : AuditableService(auditedInteractionService) {

@Transactional
fun insertPerson(defendant: Defendant, courtCode: String): InsertPersonResult =
audit(BusinessInteractionCode.INSERT_PERSON) { audit ->

val dateOfBirth = defendant.personDefendant?.personDetails?.dateOfBirth
?: throw IllegalArgumentException("Date of birth not found in message")

// Under 10 years old validation
dateOfBirth.let {
val age = Period.between(it, LocalDate.now()).years
require(age > 10) {
"Date of birth would indicate person is under ten years old: $it"
}
}

// Person record
val savedPerson = personRepository.save(defendant.toPerson())

Expand Down Expand Up @@ -78,7 +63,7 @@ class PersonService(

val savedEquality = equalityRepository.save(equality)

val addressInfo = defendant.personDefendant.personDetails.address
val addressInfo = defendant.personDefendant?.personDetails?.address
val osPlacesResponse = addressInfo?.takeIf { it.containsInformation() && !it.postcode.isNullOrBlank() }
?.let { findAddressByFreeText(it) }

Expand Down Expand Up @@ -122,12 +107,10 @@ class PersonService(
)
}
}

audit["offenderId"] = savedPerson.id
InsertPersonResult(savedPerson, savedManager, savedEquality, savedAddress)
}

@Transactional
fun insertAddress(address: PersonAddress): PersonAddress = audit(BusinessInteractionCode.INSERT_ADDRESS) { audit ->
val savedAddress = personAddressRepository.save(address)
audit["addressId"] = address.id!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.anyMap
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.mockito.kotlin.*
import uk.gov.justice.digital.hmpps.converter.NotificationConverter
import uk.gov.justice.digital.hmpps.data.generator.MessageGenerator
import uk.gov.justice.digital.hmpps.data.generator.PersonAddressGenerator
import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator
import uk.gov.justice.digital.hmpps.data.generator.PersonManagerGenerator
import uk.gov.justice.digital.hmpps.flags.FeatureFlags
import uk.gov.justice.digital.hmpps.integrations.client.*
import uk.gov.justice.digital.hmpps.integrations.delius.entity.Equality
import uk.gov.justice.digital.hmpps.message.Notification
Expand All @@ -40,17 +39,14 @@ internal class HandlerTest {
@Mock
lateinit var notifier: Notifier

@Mock
private lateinit var featureFlags: FeatureFlags

@InjectMocks
lateinit var handler: Handler

@Test
fun `inserts records when probation search match is not found`() {
whenever(probationSearchClient.match(any())).thenReturn(
ProbationMatchResponse(
matches = emptyList(),
matchedBy = "NONE"
)
)
whenever(personService.insertPerson(any(), any())).thenReturn(
InsertPersonResult(
person = PersonGenerator.DEFAULT,
Expand All @@ -60,6 +56,9 @@ internal class HandlerTest {
)
)

probationSearchMatchNotFound()
whenever(featureFlags.enabled("common-platform-record-creation-toggle")).thenReturn(true)

val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT)
handler.handle(notification)
verify(telemetryService).notificationReceived(notification)
Expand All @@ -70,25 +69,9 @@ internal class HandlerTest {

@Test
fun `does not insert person or address when match is found`() {
val fakeMatchResponse = ProbationMatchResponse(
matches = listOf(
OffenderMatch(
offender = OffenderDetail(
otherIds = IDs(crn = "X123456", pncNumber = "00000000000Z"),
firstName = "Name",
surname = "Name",
dateOfBirth = LocalDate.of(1980, 1, 1)
)
)
),
matchedBy = "PNC"
)

whenever(probationSearchClient.match(any())).thenReturn(fakeMatchResponse)

probationSearchMatchFound()
val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT)
handler.handle(notification)

verify(telemetryService).notificationReceived(notification)
verify(personService, never()).insertPerson(any(), any())
verify(notifier, never()).caseCreated(any())
Expand All @@ -98,9 +81,7 @@ internal class HandlerTest {
@Test
fun `When defendants with remanded in custody are not found then no inserts occur`() {
val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT_NO_REMAND)

handler.handle(notification)

verify(telemetryService).notificationReceived(notification)
verify(personService, never()).insertPerson(any(), any())
verify(notifier, never()).caseCreated(any())
Expand All @@ -110,13 +91,76 @@ internal class HandlerTest {
@Test
fun `When a defendant is missing name or dob then records are not inserted and probation-search is not performed`() {
val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT_NULL_FIELDS)
handler.handle(notification)
verify(telemetryService).notificationReceived(notification)
verify(probationSearchClient, never()).match(any())
verify(personService, never()).insertPerson(any(), any())
verify(notifier, never()).caseCreated(any())
verify(notifier, never()).addressCreated(any())
}

@Test
fun `Person created logged when feature flag enabled`() {
probationSearchMatchNotFound()

whenever(featureFlags.enabled("common-platform-record-creation-toggle")).thenReturn(true)
whenever(personService.insertPerson(any(), any())).thenReturn(
InsertPersonResult(
person = PersonGenerator.DEFAULT,
personManager = PersonManagerGenerator.DEFAULT,
equality = Equality(id = 1L, personId = 1L, softDeleted = false),
address = PersonAddressGenerator.MAIN_ADDRESS,
)
)

val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT)
handler.handle(notification)

verify(telemetryService).notificationReceived(notification)
verify(probationSearchClient, never()).match(any())
verify(telemetryService).trackEvent(eq("PersonCreated"), anyMap(), anyMap())
verify(personService).insertPerson(any(), any())
verify(notifier).caseCreated(any())
verify(notifier).addressCreated(any())
}

@Test
fun `Simulated person created logged when feature flag disabled`() {
probationSearchMatchNotFound()
whenever(featureFlags.enabled("common-platform-record-creation-toggle")).thenReturn(false)

val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT)
handler.handle(notification)

verify(telemetryService).notificationReceived(notification)
verify(telemetryService).trackEvent(eq("SimulatedPersonCreated"), anyMap(), anyMap())
verify(personService, never()).insertPerson(any(), any())
verify(notifier, never()).caseCreated(any())
verify(notifier, never()).addressCreated(any())
}

private fun probationSearchMatchNotFound() {
whenever(probationSearchClient.match(any())).thenReturn(
ProbationMatchResponse(
matches = emptyList(),
matchedBy = "NONE"
)
)
}

private fun probationSearchMatchFound() {
val fakeMatchResponse = ProbationMatchResponse(
matches = listOf(
OffenderMatch(
offender = OffenderDetail(
otherIds = IDs(crn = "X123456", pncNumber = "00000000000Z"),
firstName = "Name",
surname = "Name",
dateOfBirth = LocalDate.of(1980, 1, 1)
)
)
),
matchedBy = "PNC"
)
whenever(probationSearchClient.match(any())).thenReturn(fakeMatchResponse)
}
}
Loading