From 7050535c4e62bccf07bb133f9effa232e658a646 Mon Sep 17 00:00:00 2001 From: achimber-moj <161360519+achimber-moj@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:27:17 +0100 Subject: [PATCH] PI-2344 - add api (#4031) * PI-2344 - add api * PI-2344 - update test * Formatting changes * Empty-Commit * PI-2344 - correct role details * PI-2344 - small refactor * PI-2344 - apply review comments * Formatting changes * Empty-Commit --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../justice/digital/hmpps/data/DataLoader.kt | 13 +++- .../hmpps/data/generator/PersonGenerator.kt | 19 ++++++ .../hmpps/GetPersonByCRNIntegrationTest.kt | 59 +++++++++++++++++++ .../justice/digital/hmpps/IntegrationTest.kt | 30 ---------- .../SubjectAccessRequestsController.kt | 17 ++++++ .../justice/digital/hmpps/api/model/Person.kt | 5 ++ .../digital/hmpps/controller/ApiController.kt | 17 ------ .../delius/person/entity/Person.kt | 44 ++++++++++++++ .../service/SubjectAccessRequestsService.kt | 21 +++++++ .../src/main/resources/application.yml | 2 +- .../SubjectAccessRequestsServiceTest.kt | 50 ++++++++++++++++ 11 files changed, 227 insertions(+), 50 deletions(-) create mode 100644 projects/subject-access-requests-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt create mode 100644 projects/subject-access-requests-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/GetPersonByCRNIntegrationTest.kt delete mode 100644 projects/subject-access-requests-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt create mode 100644 projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SubjectAccessRequestsController.kt create mode 100644 projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Person.kt delete mode 100644 projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt create mode 100644 projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integration/delius/person/entity/Person.kt create mode 100644 projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SubjectAccessRequestsService.kt create mode 100644 projects/subject-access-requests-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SubjectAccessRequestsServiceTest.kt diff --git a/projects/subject-access-requests-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/subject-access-requests-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index f6ea94c9a7..6480ea934f 100644 --- a/projects/subject-access-requests-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/subject-access-requests-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -1,17 +1,21 @@ package uk.gov.justice.digital.hmpps.data import jakarta.annotation.PostConstruct +import jakarta.persistence.EntityManager import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator import uk.gov.justice.digital.hmpps.data.generator.UserGenerator import uk.gov.justice.digital.hmpps.user.AuditUserRepository @Component @ConditionalOnProperty("seed.database") class DataLoader( - private val auditUserRepository: AuditUserRepository + private val auditUserRepository: AuditUserRepository, + private val em: EntityManager ) : ApplicationListener { @PostConstruct @@ -19,7 +23,12 @@ class DataLoader( auditUserRepository.save(UserGenerator.AUDIT_USER) } + @Transactional override fun onApplicationEvent(are: ApplicationReadyEvent) { - // Perform dev/test database setup here, using JPA repositories and generator classes... + em.persistAll(PersonGenerator.PERSON1) + } + + private fun EntityManager.persistAll(vararg entities: Any) { + entities.forEach { persist(it) } } } diff --git a/projects/subject-access-requests-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt b/projects/subject-access-requests-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt new file mode 100644 index 0000000000..7f83c4fbe0 --- /dev/null +++ b/projects/subject-access-requests-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt @@ -0,0 +1,19 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.integration.delius.person.entity.Person + +object PersonGenerator { + + val PERSON1: Person = generate("A123456", "Jon", "Harry", "Fred", "Smith") + val PERSON2: Person = generate("A123456", "Jon", "Harry", surname = "Smith") + val PERSON3: Person = generate("A123456", "Jon", thirdName = "Fred", surname = "Smith") + val PERSON4: Person = generate("A123456", "Jon", surname = "Smith") + + fun generate( + crn: String, + forename: String, + secondName: String? = null, + thirdName: String? = null, + surname: String, + ) = Person(IdGenerator.getAndIncrement(), crn, forename, secondName, thirdName, surname) +} \ No newline at end of file diff --git a/projects/subject-access-requests-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/GetPersonByCRNIntegrationTest.kt b/projects/subject-access-requests-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/GetPersonByCRNIntegrationTest.kt new file mode 100644 index 0000000000..71b4486a35 --- /dev/null +++ b/projects/subject-access-requests-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/GetPersonByCRNIntegrationTest.kt @@ -0,0 +1,59 @@ +package uk.gov.justice.digital.hmpps + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import uk.gov.justice.digital.hmpps.api.model.Person +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator.PERSON1 +import uk.gov.justice.digital.hmpps.telemetry.TelemetryService +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = RANDOM_PORT) +internal class GetPersonByCRNIntegrationTest { + @Autowired + lateinit var mockMvc: MockMvc + + val crn = PERSON1.crn + + @MockBean + lateinit var telemetryService: TelemetryService + + @Test + fun `unauthorized status returned`() { + mockMvc + .perform(get("/probation-case/$crn")) + .andExpect(status().isUnauthorized) + } + + @Test + fun `API call probation record not found`() { + mockMvc + .perform(get("/probation-case/Z123456").withToken()) + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.message").value("Person with crn of Z123456 not found")) + } + + @Test + fun `API call return person data`() { + val expectedResponse = Person("Jon Harry Fred Smith") + + val response = mockMvc + .perform(get("/probation-case/$crn").withToken()) + .andExpect(status().isOk) + .andDo(print()) + .andReturn().response.contentAsJson() + + assertEquals(expectedResponse, response) + } +} diff --git a/projects/subject-access-requests-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt b/projects/subject-access-requests-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt deleted file mode 100644 index d9006c74c5..0000000000 --- a/projects/subject-access-requests-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package uk.gov.justice.digital.hmpps - -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT -import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken -import uk.gov.justice.digital.hmpps.telemetry.TelemetryService - -@AutoConfigureMockMvc -@SpringBootTest(webEnvironment = RANDOM_PORT) -internal class IntegrationTest { - @Autowired - lateinit var mockMvc: MockMvc - - @MockBean - lateinit var telemetryService: TelemetryService - - @Test - fun `API call retuns a success response`() { - mockMvc - .perform(get("/example/123").withToken()) - .andExpect(status().is2xxSuccessful) - } -} diff --git a/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SubjectAccessRequestsController.kt b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SubjectAccessRequestsController.kt new file mode 100644 index 0000000000..60b2ebb4c6 --- /dev/null +++ b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SubjectAccessRequestsController.kt @@ -0,0 +1,17 @@ +package uk.gov.justice.digital.hmpps.api.controller + +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import uk.gov.justice.digital.hmpps.service.SubjectAccessRequestsService + +@RestController +@RequestMapping("probation-case/{crn}") +@PreAuthorize("hasRole('PROBATION_API__SUBJECT_ACCESS_REQUEST__DETAIL')") +class SubjectAccessRequestsController(private val subjectAccessRequestsService: SubjectAccessRequestsService) { + + @GetMapping + fun getPersonalDetails(@PathVariable crn: String) = subjectAccessRequestsService.getPersonDetailsByCrn(crn) +} diff --git a/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Person.kt b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Person.kt new file mode 100644 index 0000000000..66f47f81c9 --- /dev/null +++ b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Person.kt @@ -0,0 +1,5 @@ +package uk.gov.justice.digital.hmpps.api.model + +data class Person( + val fullName: String +) diff --git a/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt deleted file mode 100644 index 6e4f24c46f..0000000000 --- a/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt +++ /dev/null @@ -1,17 +0,0 @@ -package uk.gov.justice.digital.hmpps.controller - -import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RestController - -@RestController -class ApiController { - @PreAuthorize("hasRole('EXAMPLE')") - @GetMapping(value = ["/example/{inputId}"]) - fun handle( - @PathVariable("inputId") inputId: String - ) { - // TODO Not yet implemented - } -} diff --git a/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integration/delius/person/entity/Person.kt b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integration/delius/person/entity/Person.kt new file mode 100644 index 0000000000..af42c1a859 --- /dev/null +++ b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integration/delius/person/entity/Person.kt @@ -0,0 +1,44 @@ +package uk.gov.justice.digital.hmpps.integration.delius.person.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable +import org.hibernate.annotations.SQLRestriction +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.exception.NotFoundException + +@Immutable +@Entity +@Table(name = "offender") +@SQLRestriction("soft_deleted = 0") +class Person( + @Id + @Column(name = "offender_id") + val id: Long, + + @Column(columnDefinition = "char(7)") + val crn: String, + + @Column(name = "first_name", length = 35) + val forename: String, + + @Column(name = "second_name", length = 35) + val secondName: String? = null, + + @Column(name = "third_name", length = 35) + val thirdName: String? = null, + + @Column(name = "surname", length = 35) + val surname: String, + + @Column(columnDefinition = "number") + val softDeleted: Boolean = false, +) + +interface PersonRepository : JpaRepository { + fun findByCrn(crn: String): Person? +} + +fun PersonRepository.getPerson(crn: String) = findByCrn(crn) ?: throw NotFoundException("Person", "crn", crn) \ No newline at end of file diff --git a/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SubjectAccessRequestsService.kt b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SubjectAccessRequestsService.kt new file mode 100644 index 0000000000..e25d179944 --- /dev/null +++ b/projects/subject-access-requests-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SubjectAccessRequestsService.kt @@ -0,0 +1,21 @@ +package uk.gov.justice.digital.hmpps.service + +import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.integration.delius.person.entity.PersonRepository +import uk.gov.justice.digital.hmpps.integration.delius.person.entity.getPerson +import uk.gov.justice.digital.hmpps.integration.delius.person.entity.Person as PersonEntity +import uk.gov.justice.digital.hmpps.api.model.Person + +@Service +class SubjectAccessRequestsService(private val personRepository: PersonRepository) { + + fun getPersonDetailsByCrn(crn: String): Person { + return personRepository.getPerson(crn).toPerson() + } +} + +fun PersonEntity.toPerson(): Person = Person(getName()) + +fun PersonEntity.getName(): String { + return listOfNotNull(forename, secondName, thirdName, surname).joinToString(" ") +} \ No newline at end of file diff --git a/projects/subject-access-requests-and-delius/src/main/resources/application.yml b/projects/subject-access-requests-and-delius/src/main/resources/application.yml index a5e4bffd87..6a82965067 100644 --- a/projects/subject-access-requests-and-delius/src/main/resources/application.yml +++ b/projects/subject-access-requests-and-delius/src/main/resources/application.yml @@ -19,7 +19,7 @@ spring: threads.virtual.enabled: true oauth2.roles: - - EXAMPLE + - PROBATION_API__SUBJECT_ACCESS_REQUEST__DETAIL springdoc.default-produces-media-type: application/json diff --git a/projects/subject-access-requests-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SubjectAccessRequestsServiceTest.kt b/projects/subject-access-requests-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SubjectAccessRequestsServiceTest.kt new file mode 100644 index 0000000000..62fbb0ee9c --- /dev/null +++ b/projects/subject-access-requests-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SubjectAccessRequestsServiceTest.kt @@ -0,0 +1,50 @@ +package uk.gov.justice.digital.hmpps.service + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.whenever +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator +import uk.gov.justice.digital.hmpps.integration.delius.person.entity.Person +import uk.gov.justice.digital.hmpps.integration.delius.person.entity.PersonRepository + +@ExtendWith(MockitoExtension::class) +class SubjectAccessRequestsServiceTest { + + @Mock + lateinit var personRepository: PersonRepository + + @InjectMocks + lateinit var subjectAccessRequestsService: SubjectAccessRequestsService + + val person1 = PersonGenerator.PERSON1 + + @ParameterizedTest + @CsvSource( + "A123456,Jon Harry Fred Smith, 1", + "A123456,Jon Harry Smith, 2", + "A123456,Jon Fred Smith, 3", + "A123456,Jon Smith, 4", + ) + fun `test returned name`(crn: String, fullName: String, person: Int) { + + whenever(personRepository.findByCrn(crn)).thenReturn(getPerson(person)) + + val response = subjectAccessRequestsService.getPersonDetailsByCrn(crn) + + assertEquals(fullName, response.fullName) + } + + private fun getPerson(person: Int): Person { + return when (person) { + 1 -> PersonGenerator.PERSON1 + 2 -> PersonGenerator.PERSON2 + 3 -> PersonGenerator.PERSON3 + else -> PersonGenerator.PERSON4 + } + } +} \ No newline at end of file