failing to add client credentials (clientid/clientsecret) at Spring Webclient: Request processing failed ... 401 UNAUTHORIZED - spring

I am trying to use WebClient to consume an endpoint which provides a token.
Using Postman it works as expected. Exported curl from postman is:
curl --location --request POST 'https://mycomp.url/api/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=xxx' \
--data-urlencode 'client_secret=yyy' \
--data-urlencode 'grant_type=client_credentials'
I am configuring webclient call based on same curl above.
Here is my WebClient config:
#Configuration
class ClientConfiguration {
#Bean
fun webClient(): WebClient = WebClient.builder()
.clientConnector(
ReactorClientHttpConnector(
HttpClient.from(
TcpClient
.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.doOnConnected { connection: Connection ->
connection.addHandlerLast(ReadTimeoutHandler(10000, TimeUnit.MILLISECONDS))
connection.addHandlerLast(WriteTimeoutHandler(10000, TimeUnit.MILLISECONDS))
}))
)
.build()
}
Here is the webclient post in order to recieve a token:
#Service
class TokenService(private val webClient: WebClient) {
fun postAsynchronous(): Mono<TokenResponse> = webClient
.post()
.uri(UriComponentsBuilder
.fromHttpUrl("https://mycomp.url")
.path("/api/oauth/token")
.build()
.toUri())
.header("grant_type","client_credentials")
.header("client_id","xxx")
.header("client_secret","yyy")
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.retrieve()
.onStatus(HttpStatus::is4xxClientError) { Mono.error(RuntimeException("4XX Error ${it.statusCode()}")) }
.onStatus(HttpStatus::is5xxServerError) { Mono.error(RuntimeException("5XX Error ${it.statusCode()}")) }
.bodyToMono(TokenResponse::class.java)
}
Here is my build.gradle.kts (the relevant part):
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.jetbrains.kotlin.jvm") version "1.4.10"
id("org.jetbrains.kotlin.kapt") version "1.4.10"
kotlin("plugin.spring") version "1.5.20"
id("org.springframework.boot") version "2.4.7"
//kotlin("jvm") version "1.5.30"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
}
val kotlinVersion: String by project
val springVersion: String by project
val projectGroupId: String by project
val projectVersion: String by project
group = projectGroupId
version = projectVersion
repositories {
mavenLocal()
... some internal artifactories
mavenCentral()
}
// add dependencies
dependencies {
kapt(kotlin("stdlib", kotlinVersion))
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect", kotlinVersion))
implementation("org.springframework.boot:spring-boot-dependencies:2.4.7")
implementation("org.springframework.boot:spring-boot-starter:2.4.7")
implementation("org.springframework.boot:spring-boot-starter-web:2.4.7")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.cloud:spring-cloud-starter-openfeign:3.0.3")
implementation("io.github.openfeign:feign-okhttp:10.2.0")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.2")
}
The whole exception is:
2021/09/23 17:33:53.123 [http-nio-8080-exec-2] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2021/09/23 17:33:53.123 [http-nio-8080-exec-2] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
2021/09/23 17:33:53.124 [http-nio-8080-exec-2] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
2021/09/23 17:33:54.396 [http-nio-8080-exec-2] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 4XX Error 401 UNAUTHORIZED] with root cause
java.lang.RuntimeException: 4XX Error 401 UNAUTHORIZED
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:32)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ 401 from POST https://mycomp-url/api/oauth/token [DefaultWebClient]
Stack trace:
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:32)
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:15)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec$StatusHandler.apply(DefaultWebClient.java:693)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.applyStatusHandlers(DefaultWebClient.java:652)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.handleBodyMono(DefaultWebClient.java:621)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.lambda$bodyToMono$2(DefaultWebClient.java:541)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
I tried also other approach just in case.
I keep webclient as it is and I just change how I send the credentials.
Firstly I created a simple class containing all three parameters:
data class TokenRequest(
var grantType: String,
var clientId: String,
var clientSecret: String
)
And then I modified the webclient.post to
fun postAsynchronous(): Mono<TokenResponse> = webClient
.post()
.uri(UriComponentsBuilder
.fromHttpUrl("https://mycomp-url")
.path("/api/oauth/token")
.build()
.toUri())
.body(BodyInserters.fromValue(TokenRequest("client_credentials","xxx", "yyy")))
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.retrieve()
.onStatus(HttpStatus::is4xxClientError) { Mono.error(RuntimeException("4XX Error ${it.statusCode()}")) }
.onStatus(HttpStatus::is5xxServerError) { Mono.error(RuntimeException("5XX Error ${it.statusCode()}")) }
.bodyToMono(TokenResponse::class.java)
And I got exact same issue:
2021/09/23 18:01:55.994 [http-nio-8080-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 4XX Error 401 UNAUTHORIZED] with root cause
java.lang.RuntimeException: 4XX Error 401 UNAUTHORIZED
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:32)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ 401 from POST https://mycomp.url/api/oauth/token [DefaultWebClient]
Stack trace:
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:32)
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:15)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec$StatusHandler.apply(DefaultWebClient.java:693)
*** Edited in Oct 7th 2021
With Aniket Singla proposal I reached this new issue:
[reactor-tcp-nio-2] WARN r.n.http.client.HttpClientConnect - [id:9270e5dc-1, L:/10.92.12.165:58268 - R:mycomp-url/x.x.x.x:443] The connection observed an error
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=com.mycomp.application.models.token.TokenRequest
at org.springframework.web.reactive.function.BodyInserters.unsupportedError(BodyInserters.java:391)
...
[http-nio-8080-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.reactive.function.client.WebClientRequestException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=com.mycomp.application.models.token.TokenRequest; nested exception is org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=com.mycomp.application.models.token.TokenRequest] with root cause
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=com.mycomp.application.models.token.TokenRequest
With Maciej Dobrowolski proposal I got this new exception:
2021/10/07 17:36:29.098 [http-nio-8080-exec-2] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.core.codec.DecodingException: JSON decoding error: Instantiation of [simple type, class com.mycomp.application.models.token.TokenResponse] value failed for JSON property result due to missing (therefore NULL) value for creator parameter result which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.mycomp.application.models.token.TokenResponse] value failed for JSON property result due to missing (therefore NULL) value for creator parameter result which is a non-nullable type
at [Source: (io.netty.buffer.ByteBufInputStream); line: 8, column: 1] (through reference chain: com.mycomp.application.models.token.TokenResponse["result"])] with root cause
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.mycomp.application.models.token.TokenResponse] value failed for JSON property result due to missing (therefore NULL) value for creator parameter result which is a non-nullable type
at [Source: (io.netty.buffer.ByteBufInputStream); line: 8, column: 1] (through reference chain: com.mycomp.application.models.token.TokenResponse["result"])
at com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:112)
*** Edited
data class TokenResponse (
val result: String
)

Using --data-urlencode curl option, you are adding a parameter to the request's body. In your Kotlin code, you are not passing the same data in the request's body, but in the headers.
What you should do (to mimic postman behavior) is to pass grant_type, client_id, client_secret in the request body by using BodyInserters, like this:
webClient
.post()
.uri(UriComponentsBuilder
.fromHttpUrl("https://mycomp.url")
.path("/api/oauth/token")
.build()
.toUri())
.body(BodyInserters.fromFormData("grant_type", "client_credentials")
.with("client_id", "xxx")
.with("client_secret", "yyy"))
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.retrieve()
// ...

Supplying url encoded data in headers wont work , you just need to tell in headers that you are going to use "application/x-www-form-urlencoded" as content type, else will be taken care by webclient to convert the body into url encoded form. Made some changes to your postAsynchronous method, should solve your problem.
fun postAsynchronous(): Mono<TokenResponse> = webClient
.post()
.uri(UriComponentsBuilder
.fromHttpUrl("https://des-sts-int.mbi.cloud.ihf")
.path("/api/oauth/token")
.build()
.toUri())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.body(BodyInserters.fromFormData("grant_type", "client_credentials")
.with("client_id", "xxx")
.with("client_secret", "yyy")) )
.retrieve()
.onStatus(HttpStatus::is4xxClientError) { Mono.error(RuntimeException("4XX Error ${it.statusCode()}")) }
.onStatus(HttpStatus::is5xxServerError) { Mono.error(RuntimeException("5XX Error ${it.statusCode()}")) }
.bodyToMono(TokenResponse::class.java)

Related

springcloud cannot regonize the uri

I am going to use the web client to call api from other microservice.However, the web client cannot regconzied my url and give the error.
2023-01-23T17:10:17.261+08:00 ERROR 114017 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from UNKNOWN ] with root cause
org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from UNKNOWN
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:309) ~[spring-webflux-6.0.3.jar:6.0.3]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ 400 BAD_REQUEST from GET http:/localhost:8082/Checkuser/252 [DefaultWebClient]
Original Stack Trace:
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:309) ~[spring-webflux-6.0.3.jar:6.0.3]
at org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:213) ~[spring-webflux-6.0.3.jar:6.0.3]
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106) ~[reactor-core-3.5.1.jar:3.5.1]
at reactor.core.publisher.FluxOnErrorReturn$ReturnSubscriber.onNext(FluxOnErrorReturn.java:162) ~[reactor-core-3.5.1.jar:3.5.1]
at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:122) ~[reactor-core-3.5.1.jar:3.5.1]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129) ~[reactor-core-3.5.1.jar:3.5.1]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.5.1.jar:3.5.1]
at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299) ~[reactor-core-3.5.1.jar:3.5.1]
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337) ~[reactor-core-3.5.1.jar:3.5.1]
at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2071) ~[reactor-core-3.5.1.jar:3.5.1]
at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145) ~[reactor-core-3.5.1.jar:3.5.1]
at
My code:
Boolean result=webClientBuilder.build().get()
.uri(uriBuilder -> uriBuilder
.path("http://localhost:8082/Checkuser/{id}")//"http://localhost:8082/Checkuser/{id}")
.build(252))
.retrieve()
.bodyToMono(Boolean.class)
.block(); //make syn request
if(Boolean.FALSE.equals(result)){
throw new IllegalAccessException("User score to low");
My config file:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
#Configuration
public class webclientconfig {
#Bean
public WebClient webClient(){
return WebClient.builder().build();
}
#Bean
#LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder(){
return WebClient.builder();
}
}
The webclient automatically ignore my "/" after http which i have typed

Pass through SOAP proxy spring Unsupported media type multipart/related; type="application/xop+xml"; boundary

Hi I am trying to implement a pass through SOAP proxy via #RestController in spring. For this purpose I have mapped a rest controller in following way:
#RestController
class MyProxy {
#PostMapping(value = "/**")
public ResponseEntity<String> proxyPost(#RequestBody(required = false) String body, HttpServletRequest request) {}
}
The regular SOAP requests are going OK. The problem comes when a MTOM type of SOAP request is send via the proxy. Then spring failes with unrecognized content type. Here is the exception:
Caused by: org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is javax.servlet.ServletException: Unsupported Content-Type [multipart/related; type="application/xop+xml"; boundary="uuid:dacf4733-80b4-41bc-b2e1-db69b6beadf6"; start="<root.message#cxf.apache.org>"; start-info="text/xml"], expected [multipart/form-data]
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.handleParseFailure(StandardMultipartHttpServletRequest.java:124)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:115)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:88)
at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:122)
at org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1205)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
... 60 common frames omitted
Caused by: javax.servlet.ServletException: Unsupported Content-Type [multipart/related; type="application/xop+xml"; boundary="uuid:dacf4733-80b4-41bc-b2e1-db69b6beadf6"; start="<root.message#cxf.apache.org>"; start-info="text/xml"], expected [multipart/form-data]
at org.eclipse.jetty.server.Request.getParts(Request.java:2407)
at javax.servlet.http.HttpServletRequestWrapper.getParts(HttpServletRequestWrapper.java:317)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:95)
... 66 common frames omitted
When receiving a multipart/* request Spring delegates this to the configured Multipart handler. This is enabled by default and for this case should be disabled.
spring.servlet.multipart.enabled=false
Adding the above to your properties should disable it and prevent the parsing, so you can handle it in your controller.

Spring cloud GCP com.google.cloud.storage.StorageException access_token not found error

Spring boot-2.3.10, Spring Cloud Gcp: 1.2.8
I'm trying to access specific image pattern **(/resources/images/specific_folder/****) from GC Storage. For that I wrote the resource handler as shown below:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("Setting the resource location {}", gcStorageLocation);
registry.addResourceHandler("/resources/images/specific_folder/**").addResourceLocations("gs:bucket_name/storage/images/specific_folder/").setCachePeriod(3600).resourceChain(true)
.addResolver(new GcStorageResolver());
}
GcStorageResolver.java extends AbstractResourceResolver.java
#Override
protected Resource resolveResourceInternal(#Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations,
ResourceResolverChain chain) {
log.info("resolveResourceInternal called for request: {}, requestPath: {}", request.getRequestURL(), requestPath);
return getResource(requestPath, request, locations);
}
I verified that a valid GoogleStorageResource is being returned along with credential. But somewhere in the spring chain, I'm getting the below error:
2021-06-25 15:40:23.366 ERROR 4676 --- [nio-8080-exec-1]
o.a.c.c.C.[.[.[.[dispatcherServlet] 175 : Servlet.service() for
servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; nested exception is
com.google.cloud.storage.StorageException: Error parsing token refresh
response. Expected value access_token not found.] with root cause
java.io.IOException: Error parsing token refresh response. Expected
value access_token not found. at
com.google.auth.oauth2.OAuth2Utils.validateString(OAuth2Utils.java:113)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.oauth2.ServiceAccountCredentials.refreshAccessToken(ServiceAccountCredentials.java:449)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.oauth2.OAuth2Credentials.refresh(OAuth2Credentials.java:157)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.oauth2.OAuth2Credentials.getRequestMetadata(OAuth2Credentials.java:145)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.oauth2.ServiceAccountCredentials.getRequestMetadata(ServiceAccountCredentials.java:603)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.http.HttpCredentialsAdapter.initialize(HttpCredentialsAdapter.java:91)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.cloud.http.HttpTransportOptions$1.initialize(HttpTransportOptions.java:159)
~[google-cloud-core-http-1.94.0.jar:1.94.0] at
com.google.cloud.http.CensusHttpModule$CensusHttpRequestInitializer.initialize(CensusHttpModule.java:109)
~[google-cloud-core-http-1.94.0.jar:1.94.0] at
com.google.api.client.http.HttpRequestFactory.buildRequest(HttpRequestFactory.java:88)
~[google-http-client-1.38.0.jar:1.38.0]
Not sure what's going on here. Any pointers?

Eureka on Cloudfoundry RestTemplate gets 301 Moved Permanently

I’m setting up a Spring Boot microservice infrastructure with a Eureka Service Registry.
I’m using RestTemplate to call another service (resolution done via Eureka) locally it works perfect! But on Cloud Foundry I always get a “301 Moved permanently” errorcode when calling the service.
Anyone knows if there is a specific configuration necessary for RestTemplate to work with Eureka on Cloud Foundry?
#Bean
#LoadBalanced
RestTemplate getRestTemplate() {
return new RestTemplate();
}
public UserMapping getUserMappingFromRemoteServer(String name_id){
UserMapping userMappingResponse = mappingTemplate.getForObject("http://user-mapping/user?id=" + name_id, UserMapping.class);
}
My response is always
Setting request Accept header to [application/json, application/*+json]
Created GET request for "http://user-mapping/user?id=1"
GET request for "http://user-mapping/user?id=1" resulted in 301 (MOVED_PERMANENTLY)
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.user.SmartCharging.UserMapping] and content type [text/html]]
eureka:
instance:
non-secure-port-enabled: false
secure-port-enabled: true
did the job

Testing Rest Controllers in Spring Boot using Standalone MockMvc

I am trying to put some unit/integration tests on my code,
I've a typical rest application build with spring boot, I am trying to test the AdminController save method:
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<Void> save(#RequestBody #Valid User user) {
user.setRole(Role.ROLE_ADMIN);
user.setPassword(passwordEncoder.encode(user.getPassword()));
return super.save(user);
}
The method is straight forward and it check the duplicate through the database, as I making the User object's username property unique:
#NotNull
#Column(updatable = false, unique = true)
private String username;
When I try to save two users with the same username, the server refuses with this out-of-the-box JSON output: (Http code: 500)
{
"timestamp": 1473942296273,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.dao.DataIntegrityViolationException",
"message": "could not execute statement; SQL [n/a]; constraint [uk_sb8bbouer5wak8vyiiy4pf2bx]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
"path": "/api/admins"
}
I am ok with this, as it appears spring boot default behaviour is to send errors to /error (which is handled by BasicErrorController) that handle the exception and return this pretty json output:
[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [uk_sb8bbouer5wak8vyiiy4pf2bx]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing POST request for [/error]
RequestMappingHandlerMapping : Looking up handler method for path /error
RequestMappingHandlerMapping : Returning handler method [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]
DefaultListableBeanFactory : Returning cached instance of singleton bean 'basicErrorController'
OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
HttpEntityMethodProcessor : Written [{timestamp=Thu Sep 15 16:09:07 AST 2016, status=500, error=Internal Server Error, exception=org.springframework.dao.DataIntegrityViolationException, message=could not execute statement; SQL [n/a]; constraint [uk_sb8bbouer5wak8vyiiy4pf2bx]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement, path=/api/admins}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#1482cc8]
But when I try to test this using mockito (see full test class):
#Transactional
#Test
public void testSaveDupValidUser() throws Exception {
User user = new User();
user.setUsername("admin");
this.mockMvc.perform(post("/api/admins")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(user)))
.andDo(print())
.andExpect(status().isOk());
this.mockMvc.perform(post("/api/admins")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(user)))
.andDo(print())
.andExpect(status().isInternalServerError());
}
instead of getting 500 internal server error, I got the following exception from junit:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["UK_SB8BBOUER5WAK8VYIIY4PF2BX_INDEX_3 ON PUBLIC.""user""(USERNAME) VALUES ('admin', 1)"; SQL statement:
insert into "user" (id, created_by, created_date, modified_by, modified_date, enabled, password, role, username) values (null, ?, ?, ?, ?, ?, ?, ?, ?) [23505-192]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
So, My question is:
How to enable make the dispatcher servlet to forward to /error and return the same error as if the application is running (not under test)?
while keeping use MockMvc standealone, and without writing code in the rest method to handle the exception and return code 500.
(I think this might be impossible, so Should I assert on the exception instead? Or should I use webAppContextSetup? What is the best practice here? - jHipster for example uses standalone MockMvc in all its tests)
Related: unit test Spring MissingServletRequestParameterException JSON response (but mine not fixed)
During runtime spring registers a error page at the servlet container and forwards all not handled errors there.
The MockMvcServer does not fully support forwarding, and I think that is the reason why you see a different result in your tests.
When saving a user fails, because another one with the same name already exists, this is not an (unexpected) internal server error. Instead you should catch the exception in the controller and return a HTTP 400 (Bad Request). This tells the client, the server is ok, but something with the request is not.
You can add a ExceptionHandler to create the response for different types of exceptions.
All HTTP 400-499 are client errors, so they will not be forwarded to the error page, which is intended.
If the exception is handled, you should also receive the correct Http status and response in the MockMvc test.

Resources