Skip to content

Commit

Permalink
CDPS-1054: Added basic tests for prototype functionality and applied …
Browse files Browse the repository at this point in the history
…auto-formating.
  • Loading branch information
mtac50 committed Nov 19, 2024
1 parent c8b2da2 commit 6fa3703
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ package uk.gov.justice.digital.hmpps.personintegrationapi.common

object Constants {
const val PRISONER_NUMBER_REGEX = "^[A-Za-z0-9]{1,10}\$"
const val PRISONER_NUMBER_VALIDATION_MESSAGE = "The prisoner number must be a alphanumeric string upto 10 characters in length."
const val PRISONER_NUMBER_VALIDATION_MESSAGE =
"The prisoner number must be a alphanumeric string upto 10 characters in length."
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import uk.gov.justice.digital.hmpps.personintegrationapi.common.Constants
AnnotationTarget.FIELD,
AnnotationTarget.VALUE_PARAMETER,
)
@kotlin.annotation.Retention(
@Retention(
AnnotationRetention.RUNTIME,
)
annotation class ValidPrisonerNumber
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ class OpenApiConfiguration(
.servers(
listOf(
Server().url("http://localhost:8080").description("Local"),
Server().url("https://person-integration-api-dev.hmpps.service.justice.gov.uk").description("Development"),
Server().url("https://person-integration-api-preprod.hmpps.service.justice.gov.uk").description("Pre-Production"),
Server().url("https://person-integration-api.hmpps.service.justice.gov.uk").description("Production"),
Server().url("https://person-integration-api-dev.hmpps.service.justice.gov.uk")
.description("Development"),
Server().url("https://person-integration-api-preprod.hmpps.service.justice.gov.uk")
.description("Pre-Production"),
Server().url("https://person-integration-api.hmpps.service.justice.gov.uk")
.description("Production"),
),
)
.info(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import org.springframework.util.MultiValueMap
import java.util.*

@SuppressWarnings("unchecked")
class UserEnhancedOAuth2ClientCredentialGrantRequestConverter : OAuth2ClientCredentialsGrantRequestEntityConverter() {
class UserEnhancedOAuth2ClientCredentialGrantRequestConverter :
OAuth2ClientCredentialsGrantRequestEntityConverter() {

fun enhanceWithUsername(grantRequest: OAuth2ClientCredentialsGrantRequest?, username: String?): RequestEntity<Any> {
fun enhanceWithUsername(
grantRequest: OAuth2ClientCredentialsGrantRequest?,
username: String?,
): RequestEntity<Any> {
val request = super.convert(grantRequest)
val headers = request.headers
val body = Objects.requireNonNull(request).body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentia
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction
import org.springframework.web.context.annotation.RequestScope
import org.springframework.web.reactive.function.client.ExchangeFilterFunction
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.support.WebClientAdapter
import org.springframework.web.service.invoker.HttpServiceProxyFactory
Expand All @@ -32,7 +31,8 @@ class WebClientConfiguration(
@Value("\${api.timeout:90s}") val timeout: Duration,
) {
@Bean
fun authHealthWebClient(builder: WebClient.Builder): WebClient = builder.healthWebClient(authBaseUri, healthTimeout)
fun authHealthWebClient(builder: WebClient.Builder): WebClient =
builder.healthWebClient(authBaseUri, healthTimeout)

@Bean
fun prisonApiHealthWebClient(builder: WebClient.Builder): WebClient =
Expand All @@ -49,14 +49,14 @@ class WebClientConfiguration(
builder,
prisonApiBaseUri,
"prison-api",
null,
)
}

@Bean
@DependsOn("prisonApiWebClient")
fun prisonApiClient(prisonApiWebClient: WebClient): PrisonApiClient {
val factory = HttpServiceProxyFactory.builderFor(WebClientAdapter.create(prisonApiWebClient)).build()
val factory =
HttpServiceProxyFactory.builderFor(WebClientAdapter.create(prisonApiWebClient)).build()
val client = factory.createClient(PrisonApiClient::class.java)
return client
}
Expand All @@ -74,7 +74,9 @@ class WebClientConfiguration(

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials { clientCredentialsGrantBuilder: OAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilder ->
clientCredentialsGrantBuilder.accessTokenResponseClient(defaultClientCredentialsTokenResponseClient)
clientCredentialsGrantBuilder.accessTokenResponseClient(
defaultClientCredentialsTokenResponseClient,
)
}
.build()

Expand All @@ -87,17 +89,10 @@ class WebClientConfiguration(
builder: WebClient.Builder,
rootUri: String,
registrationId: String,
filterFunctions: List<ExchangeFilterFunction>?,
): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultClientRegistrationId(registrationId)

val standardBuild = builder.baseUrl(rootUri).apply(oauth2Client.oauth2Configuration())

if (filterFunctions.isNullOrEmpty()) {
return standardBuild.build()
}

return standardBuild.filters { it.addAll(0, filterFunctions.toList()) }.build()
return builder.baseUrl(rootUri).apply(oauth2Client.oauth2Configuration()).build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ data class ReferenceDataDomainDto(
@Schema(description = "Short code for the reference data domain", example = "COUNTRY")
val code: String,

@Schema(description = "Description of the reference data domain", example = "Countries reference data")
@Schema(
description = "Description of the reference data domain",
example = "Countries reference data",
)
val description: String,

@Schema(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class CorePersonRecordV1Resource(
@PreAuthorize("hasRole('${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_WRITE_ROLE}')")
fun putProfileImageByPrisonerNumber(
@RequestParam(required = true) @Valid @ValidPrisonerNumber prisonerNumber: String,
@RequestPart(name = "Image file", required = true) profileImage: MultipartFile,
@RequestPart(name = "imageFile", required = true) profileImage: MultipartFile,
): ResponseEntity<InputStreamResource> {
val inputStreamResource = InputStreamResource(profileImage.inputStream)
return ok().contentType(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.common.config

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest
import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
import org.springframework.util.LinkedMultiValueMap
import uk.gov.justice.digital.hmpps.personintegrationapi.config.UserEnhancedOAuth2ClientCredentialGrantRequestConverter

class UserEnhancedOAuth2ClientCredentialGrantRequestConverterTest {

private lateinit var oAuth2ClientCredentialsGrantRequest: OAuth2ClientCredentialsGrantRequest

private val underTest: UserEnhancedOAuth2ClientCredentialGrantRequestConverter =
UserEnhancedOAuth2ClientCredentialGrantRequestConverter()

@BeforeEach
fun setUp() {
oAuth2ClientCredentialsGrantRequest = OAuth2ClientCredentialsGrantRequest(CLIENT_REGISTRATION)
}

@Test
fun `client credentials grant request has the username added`() {
val response = underTest.enhanceWithUsername(oAuth2ClientCredentialsGrantRequest, TEST_USERNAME)
assertThat((response.body as LinkedMultiValueMap<*, *>)["username"]).isEqualTo(listOf(TEST_USERNAME))
}

companion object {
const val TEST_USERNAME = "TEST_USERNAME"
val CLIENT_REGISTRATION: ClientRegistration =
ClientRegistration
.withRegistrationId("test_id")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.tokenUri("oauth/token")
.scope("read")
.userNameAttributeName("id")
.clientName("Client Name")
.clientId("client-id")
.clientSecret("client-secret").build()
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.resource

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.InputStreamResource
import org.springframework.http.MediaType
import org.springframework.http.client.MultipartBodyBuilder
import org.springframework.mock.web.MockMultipartFile
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.reactive.function.BodyInserters
import uk.gov.justice.digital.hmpps.personintegrationapi.common.dto.ReferenceDataCodeDto
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.CorePersonRecordRoleConstants
import uk.gov.justice.digital.hmpps.personintegrationapi.integration.IntegrationTestBase
import uk.gov.justice.digital.hmpps.personintegrationapi.integration.wiremock.PRISONER_NUMBER
Expand Down Expand Up @@ -72,11 +80,92 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {

@DisplayName("PUT v1/core-person-record/profile-image")
@Nested
inner class PutProfileImageByPrisonerNumberTest
inner class PutProfileImageByPrisonerNumberTest {
@Nested
inner class Security {

@Test
fun `access forbidden when no authority`() {
webTestClient.put()
.uri("/v1/core-person-record/profile-image?prisonerNumber=$PRISONER_NUMBER")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(MULTIPART_BUILDER.build()))
.exchange()
.expectStatus().isUnauthorized
}

@Test
fun `access forbidden with wrong role`() {
webTestClient.put()
.uri("/v1/core-person-record/profile-image?prisonerNumber=$PRISONER_NUMBER")
.contentType(MediaType.MULTIPART_FORM_DATA)
.headers(setAuthorisation(roles = listOf("ROLE_IS_WRONG")))
.body(BodyInserters.fromMultipartData(MULTIPART_BUILDER.build()))
.exchange()
.expectStatus().isForbidden
}
}

@Nested
inner class HappyPath {

@Test
fun `can update core person record profile image by prisoner number`() {
val response = webTestClient.put()
.uri("/v1/core-person-record/profile-image?prisonerNumber=$PRISONER_NUMBER")
.contentType(MediaType.MULTIPART_FORM_DATA)
.headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_WRITE_ROLE)))
.body(BodyInserters.fromMultipartData(MULTIPART_BUILDER.build()))
.exchange()
.expectStatus().isOk
.expectBody(InputStreamResource::class.java)
.returnResult().responseBody

assertThat(response?.filename).isEqualTo(MULTIPART_FILE.originalFilename)
assertThat(response?.contentAsByteArray).isEqualTo(MULTIPART_FILE.bytes)
}
}
}

@DisplayName("GET v1/core-person-record/reference-data/domain/{domain}/codes")
@Nested
inner class GetReferenceDataCodesByDomain
inner class GetReferenceDataCodesByDomain {

@Nested
inner class Security {
@Test
fun `access forbidden when no authority`() {
webTestClient.get().uri("/v1/core-person-record/reference-data/domain/$TEST_DOMAIN/codes")
.exchange()
.expectStatus().isUnauthorized
}

@Test
fun `access forbidden with wrong role`() {
webTestClient.get().uri("/v1/core-person-record/reference-data/domain/$TEST_DOMAIN/codes")
.headers(setAuthorisation(roles = listOf("ROLE_IS_WRONG")))
.exchange()
.expectStatus().isForbidden
}
}

@Nested
inner class HappyPath {

@Test
fun `can update core person record profile image by prisoner number`() {
val response =
webTestClient.get().uri("/v1/core-person-record/reference-data/domain/$TEST_DOMAIN/codes")
.headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_ROLE)))
.exchange()
.expectStatus().isOk
.expectBodyList(ReferenceDataCodeDto::class.java)
.returnResult().responseBody

assertThat(response).isEqualTo(emptyList<ReferenceDataCodeDto>())
}
}
}

private companion object {

Expand All @@ -88,5 +177,20 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
"fieldValue": "London"
}
""".trimIndent()

val MULTIPART_FILE: MultipartFile = MockMultipartFile(
"file",
"filename.jpg",
MediaType.IMAGE_JPEG_VALUE,
"I AM A JPEG, HONEST...".toByteArray(),
)

const val TEST_DOMAIN = "COUNTRY"

val MULTIPART_BUILDER =
MultipartBodyBuilder().apply {
part("imageFile", ByteArrayResource(MULTIPART_FILE.bytes))
.header("Content-Disposition", "form-data; name=imageFile; filename=filename.jpg")
}
}
}
Loading

0 comments on commit 6fa3703

Please sign in to comment.