Testing Rest Controllers in Spring Boot using Standalone MockMvc - spring

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.

Related

Spring Boot #RequestScope and Hibernate schema based multi-tenancy

I'm working on a schema based multi-tenant app, in which I want to resolve the Tenant Identifier using a #RequestScope bean. My understanding is that #RequestScope uses/injects proxies for the request scoped beans, wherever they are referred (e.g. in other singleton beans). However, this is not working in the #Component that implements CurrentTenantIdentifierResolver and I get the following error when I start my service,
Caused by: org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name 'scopedTarget.userContext': Scope 'request' is not active for the current thread;
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
Following are the relevant pieces of code.
#Component
public class CurrentTenant implements CurrentTenantIdentifierResolver {
#Autowired
private UserContext userContext;
#Override
public String resolveCurrentTenantIdentifier() {
return Optional.of(userContext)
.map(u -> u.getDomain())
.get();
}
#Component
#RequestScope
public class UserContext {
private UUID id;
private String domain;
My questions,
Isn't the proxy for the #RequestScope injected (by default)? Do I need to do anything more?
Is Hibernate/Spring trying to establish a connection to the DB at startup (even when there is no tenant available)?
Hibernate properties:
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
properties.remove(AvailableSettings.DEFAULT_SCHEMA);
properties.put(AvailableSettings.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
properties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver);
properties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
For the time being, I'm preventing the NullPointerException by checking if we are in the RequestContext. However, a connection still gets established to the master database (although I've explicitly specified the dialect and am not specifying hbm2ddl.auto). Since this connection is not associated with any schema, I'd like to avoid making it, so that it does not look for any tables that it won't find anyways.
What seems to be happenning is that when a HTTP request is received, hibernate is trying to resolve the current tenant identifier, even before my #RequestScope bean is created (and even before my #RestController method is called.) If a provide the default connection to the databse, I then get the following error. If I don't provide a connection, it throws an exception and aborts.
2021-09-26 11:55:44.882 WARN 19759 --- [nio-8082-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 42P01
2021-09-26 11:55:44.882 ERROR 19759 --- [nio-8082-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: relation "employees" does not exist
Position: 301
2021-09-26 11:55:44.884 ERROR 19759 --- [nio-8082-exec-2] o.t.n.controller.EmployeeController : Exception: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet

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?

Spring Boot JPA native query for call PostgreSQL function

I am not familiar with the SQL function with spring boot. can anyone check my spring boot query whether it is correct?
I have created a PostgreSQL function and implemented it with spring boot.
but it is getting an error when I call the JPA query. below I have attached the SQL function and JPA query
PostgreSQL function - it is working fine
select pulse.fn_get_ticket_details_json(''::text,null::date,null::date,''::text)
Spring boot query
#Query(value = "select pulse.fn_get_ticket_details_json(''::text,null::date,null::date,''::text)", nativeQuery = true)
JsonObject SearchData();
Error message
ERROR: syntax error at or near ":"
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause
org.postgresql.util.PSQLException: ERROR: syntax error at or near ":"
Position: 44
screenshot
This should work
#Query(value = "select pulse.fn_get_ticket_details_json(''\\:\\:text,null\\:\\:date,null\\:\\:date,''\\:\\:text)", nativeQuery = true)
Remove extra spaces and check your simple quotes.

Getting Postgres Table Not Found Error with #ReadOnly Annotation in Spring Boot

I am getting the error PSQLException: ERROR: relation "schema.tableName" does not exist.
I have a GET API in spring which fetches data from a table. This API internally uses a custom method to access data using JpaReporitory. This custom method is annotated with #ReadOnly and #Transactional annotations since this API only fetches data from table. This gives me the table not found error.
But when I remove the #ReadOnly annotation, the API works just fine.
Below are the signatures of the said API and the custom method that it uses:
API:
#GetMapping("/houses/{houseId}/furnitures")
public List<Furnitures> getFurnitureslist(#PathVariable("houseId") long houseId) throws InterruptedException, ExecutionException {
return this.houseService.getFurnituresList(houseId);
}
Custom method:
#Transactional
#ReadOnly
public List<Asset> getTripPicklist(long tripId){
//Bunch of read operations using jpa repository.
}
This gives me an error trace as below:
org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
Caused by: org.postgresql.util.PSQLException: ERROR: relation "schema.tableName" does not exist
But when I remove the #ReadOnly annotation from my custom method, the API works fine and I am able fetch the expected results from the database. What am I doing wrong?

Running a native insert query in spring boot

I am trying to run a simple insert query in spring boot but I am unable to do so.
Query:
#Modifying
#Query(value = "insert into Local(userId,name,address,pin) VALUES (:userId,:name,:address,:pin)", nativeQuery = true)
List < Local > insertattributes(#Param("userId")String userId,#Param("address") String address ,#Param("name")String name,#Param("pin") String pin);
Controller:
#RequestMapping("insertattributes/{userId}/{name}/{address}/{pin}")
#ResponseBody
public List < Local > insertAttributes(#PathVariable String userId, #PathVariable String name, #PathVariable String address, #PathVariable String pin) {
return localService.insertattributes(userId,name,address,pin);
}
Error:
o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: S1009
o.h.engine.jdbc.spi.SqlExceptionHelper : Can not issue data manipulation statements with executeQuery().
[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: could not extract ResultSet; nested exception is org.hibernate.exception.GenericJDBCException: could not extract ResultSet] with root cause
java.sql.SQLException: Can not issue data manipulation statements with executeQuery().
An insert statement doesn't return the inserted record. Since you're expecting a list to be returned executeQuery() is executed by Spring. What you want is the executiong of executeUpdate() which is only executed for void methods.
See also Cannot issue data manipulation statements with executeQuery()

Resources