Skip to content

Commit

Permalink
Merge pull request #12 from isel-leic-daw/feature/main/gh-2-handling-…
Browse files Browse the repository at this point in the history
…exceptions

gh-2: adds exception handling example
  • Loading branch information
pmhsfelix authored Dec 1, 2022
2 parents edc0d9b + f8ffc21 commit eb4b891
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 1 deletion.
1 change: 1 addition & 0 deletions code/tic-tac-tow-service/docs/problems/insecure-password
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The provided password doesn't comply with the minimum security requirements...
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The request content is not valid.
1 change: 1 addition & 0 deletions code/tic-tac-tow-service/docs/problems/user-already-exists
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The user being created already exists.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The provided username or password are invalid.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Problem(
fun response(status: Int, problem: Problem) = ResponseEntity
.status(status)
.header("Content-Type", MEDIA_TYPE)
.body(problem)
.body<Any>(problem)

val userAlreadyExists = Problem(
URI(
Expand All @@ -34,5 +34,12 @@ class Problem(
"docs/problems/user-or-password-are-invalid"
)
)

val invalidRequestContent = Problem(
URI(
"https://github.com/isel-leic-daw/s2223i-51d-51n-public/tree/main/code/tic-tac-tow-service/" +
"docs/problems/invalid-request-content"
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pt.isel.daw.tictactow.http.pipeline

import org.slf4j.LoggerFactory
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.http.converter.HttpMessageNotReadableException
import org.springframework.validation.BindException
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
import pt.isel.daw.tictactow.http.model.Problem

@ControllerAdvice
class CustomExceptionHandler : ResponseEntityExceptionHandler() {

override fun handleBindException(
ex: BindException,
headers: HttpHeaders,
status: HttpStatus,
request: WebRequest
): ResponseEntity<Any> {
log.info("Handling BindException: {}", ex.message)
return Problem.response(400, Problem.invalidRequestContent)
}

override fun handleHttpMessageNotReadable(
ex: HttpMessageNotReadableException,
headers: HttpHeaders,
status: HttpStatus,
request: WebRequest
): ResponseEntity<Any> {
log.info("Handling HttpMessageNotReadableException: {}", ex.message)
return Problem.response(400, Problem.invalidRequestContent)
}

@ExceptionHandler(
Exception::class,
)
fun handleAll(): ResponseEntity<Unit> {
return ResponseEntity.status(500).build()
}

companion object {
private val log = LoggerFactory.getLogger(CustomExceptionHandler::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package pt.isel.daw.tictactow.http

import org.hamcrest.CoreMatchers.equalTo
import org.jdbi.v3.core.Jdbi
import org.junit.jupiter.api.Test
import org.postgresql.ds.PGSimpleDataSource
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Primary
import org.springframework.test.web.reactive.server.WebTestClient
import pt.isel.daw.tictactow.repository.jdbi.configure
import java.util.*

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class InternalErrorTests {

@TestConfiguration
class TestConfig {
@Bean
@Primary
fun testJdbi() = Jdbi.create(
PGSimpleDataSource().apply {
setURL("jdbc:postgresql://bad-host:5432/db?user=dbuser&password=changeit")
}
).configure()
}

@LocalServerPort
var port: Int = 0

@Test
fun `Unknown exceptions are mapped into a 500 without a response content`() {
// given: an HTTP client
val client = WebTestClient.bindToServer().baseUrl("http://localhost:$port").build()

// and: a random user
val username = UUID.randomUUID().toString()
val password = "is-this-strong?"

// when: creating a user
// then: the response is a 400 with the proper type
client.post().uri("/users")
.bodyValue(
mapOf(
"username" to username,
"password" to password
)
)
.exchange()
.expectStatus().value(equalTo(500))
.expectHeader().doesNotExist("Content-Type")
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package pt.isel.daw.tictactow.http

import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.test.web.reactive.server.WebTestClient
import java.util.*
import java.util.stream.Stream
import kotlin.test.assertTrue

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Expand Down Expand Up @@ -170,4 +173,49 @@ class UserTests {
"tic-tac-tow-service/docs/problems/insecure-password"
)
}

@ParameterizedTest
@MethodSource
fun `user creation produces an error request content is not valid`(input: Any) {
// given: an HTTP client
val client = WebTestClient.bindToServer().baseUrl("http://localhost:$port").build()

// when: creating a user with invalid data
// then: the response is a 400 with the proper type
client.post().uri("/users")
.bodyValue(
input
)
.exchange()
.expectStatus().isBadRequest
.expectHeader().contentType("application/problem+json")
.expectBody()
.jsonPath("type").isEqualTo(
"https://github.com/isel-leic-daw/s2223i-51d-51n-public/tree/main/code/" +
"tic-tac-tow-service/docs/problems/invalid-request-content"
)
}

companion object {
@JvmStatic
fun `user creation produces an error request content is not valid`(): Stream<Any> =
Stream.of(
mapOf<String, Any>(
// no username or password
),
mapOf(
"username" to "alice"
// no password
),
mapOf(
"password" to "bad",
// no username
),
mapOf(
"username" to listOf<String>(),
"password" to "changeit",
// invalid username type
),
)
}
}

0 comments on commit eb4b891

Please sign in to comment.