3

I was trying to make a unit test that verifies that an exception thrown by hibernate would be handled and would result in a proper response. The exception is a PersistenceException() with cause being a ConstraintViolationException(). So I handled it in ktor's StatusPages in the following way:

exception<PersistenceException> {
    if (it.cause is DataException) call.respond(
        HttpStatusCode.BadRequest,
        AppError("Cannot fulfill request due to Invalid Data.")
    )
    else if (it.cause is ConstraintViolationException) call.respond(
        HttpStatusCode.ExpectationFailed,
        AppError("Cannot fulfill request due to Constraint Violations.")
    )
}

And it works when I force the error by trying to insert a duplicated row but when I mock a method to throw said exception it gets encapsulated by PersistenceException twice:

  • when the error happens in real use:
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:727)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:707)
    (...)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:109)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    (...)
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "subscription_unique_topic_appid_ssl_subject_trio"
  Detail: Key (topic_id, app_id, ssl_subject_name)=(1, something, something) already exists.
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2532)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2267)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:312)
    (...)
  • when the error happens in the unit test:
javax.persistence.PersistenceException: Ex
    (Coroutine boundary)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:107)
    at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:101)
    at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:100)
    at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:140)
    at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:139)
    at io.ktor.server.testing.TestApplicationEngine$callInterceptor$1.invokeSuspend(TestApplicationEngine.kt:291)
    at io.ktor.server.testing.TestApplicationEngine$2.invokeSuspend(TestApplicationEngine.kt:50)
Caused by: javax.persistence.PersistenceException: Ex
    ... 8 more
Caused by: javax.persistence.PersistenceException: Ex
    (Coroutine boundary)
    at io.ktor.routing.Routing.executeResult(Routing.kt:147)
    at io.ktor.routing.Routing.interceptor(Routing.kt:34)
    at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:99)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:107)
    at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:101)
    at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:100)
    at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:140)
    at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:139)
    at io.ktor.server.testing.TestApplicationEngine$callInterceptor$1.invokeSuspend(TestApplicationEngine.kt:291)
    at io.ktor.server.testing.TestApplicationEngine$2.invokeSuspend(TestApplicationEngine.kt:50)
    at io.ktor.server.testing.TestApplicationEngine$handleRequest$pipelineJob$1.invokeSuspend(TestApplicationEngine.kt:290)
Caused by: org.hibernate.exception.ConstraintViolationException: Ex
    at com.controller.SubscriptionRequestControllerTest.POST request handles constraint issue with inserting in the database(SubscriptionRequestControllerTest.kt:298)
    (...)
Caused by: org.postgresql.util.PSQLException: Ex
    (...)

So it doesn't comply with the condition of 'it.cause is ConstraintViolationException' and I don't know why since I have similar unit tests throwing other exceptions and it works fine.

Here is the unit test:

fun `POST request handles constraint issue with inserting in the database`() {

    (...)

    val subscriptionsRepository = get(SubscriptionsRepository::class.java)

    every {
        subscriptionsRepository.addSubscriptionRequest(any())
    } throws PersistenceException(
        "Constraint Exception",
        ConstraintViolationException("Ex", PSQLException("Ex", PSQLState.UNIQUE_VIOLATION), "Ex")
    )
    
    (...)

    val fakeRequest = SubscriptionRequestDTO(...)

    withTestApplication(Application::main) {
        val request = handleRequest {
            method = HttpMethod.Post
            addHeader("content-type", "application/json")
            uri = "subscriptions/requests"
            setBody(Gson().toJson(fakeRequest))

            addJwtHeader(makeToken(validAlgorithm, isExpired = false))
        }
        request.response.status() shouldBe HttpStatusCode.ExpectationFailed // fails saying that the status is null
    }
}

0 Answers0