Spring Data + SpringBootTest: How to mock SecurityContext for JPA auditing? - java

In a Spring Boot application I want to test (JUnit 5) the persistence layer with enabled auditing (#EnableJpaAuditing).
I use Liquibase to setup a H2 db and Hibernate as the JPA implementation.
#Configuration
//#EnableTransactionManagement
#EnableJpaAuditing
//#EnableJpaRepositories
public class MyPersistenceConfig {
}
My entity has the following fields:
#CreatedDate
#Column(name = "CREATED_AT", updatable = false)
private Instant createdAt;
#CreatedBy
#Column(name = "CREATED_BY", updatable = false)
private String createdBy;
#CreatedDate
#Column(name = "LAST_MODIFIED_AT")
private Instant lastModifiedAt;
#CreatedBy
#Column(name = "LAST_MODIFIED_BY")
private String lastModifiedBy;
I have the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<scope>runtime</scope>
<!--<scope>test</scope>-->
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
I tried several combinations of annotations:
#SpringBootTest //(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//#DataJpaTest
#ContextConfiguration(classes = MyPersistenceConfig.class)
#EnableAutoConfiguration
//#SecurityTestExecutionListeners
//#Import(SpringBootWebSecurityConfiguration.class)
#WithMockUser(username = "test", password = "test", roles = "USER")
#ExtendWith(SpringExtension.class)
class MyRepositoryTest {
#Autowired
private MyRepository testee;
...
}
But whatever I try, either the repository is null (autowiring) or I get an exception when inserting an entry:
NULL not allowed for column "CREATED_BY"; SQL statement:
I guess I need a SecurityContext (which isn't available for autowiring currently).
What is the easiest way to provide a mock SecurityContext that works with auditing and #WithMockUser?

This is an old question but for those who may stumble upon it trying to get Spring Data auditing to work in their integration tests this may help. The auditing functionality requires an AuditingAware bean to get the current user. In DataJpaTest this seems to be missing. One way to make it available is adding a #Bean configuration to your test.
#RunWith(SpringRunner.class)
#DataJpaTest
#Import({DatabaseIntegrationTest.TestConfig.class})
#WithMockUser
class DatabaseIntegrationTest {
#TestConfiguration
static class TestConfig {
#Bean
public AuditorAware<String> auditorAware() {
return () -> Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication().getName());
}
}
}

#Before
public void setup() {
User user = userService.findByEmail("umanking#gmail.com").get();
SecurityContextHolder.getContext().setAuthentication(new Authentication() {
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public Object getCredentials() {
return user.getPassword();
}
#Override
public Object getDetails() {
return user;
}
#Override
public Object getPrincipal() {
return null;
}
#Override
public boolean isAuthenticated() {
return true;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
return user.getName();
}
});
}
maybe you can use #Before annotation

Related

Spring #Valid annotation not working as validating dto fields?

I have checked many other questions about it but I cannot find the solution, where am I missing ?
Here is the controller method:
package com.nishberkay.nishcustomer.controller;
import com.nishberkay.nishcustomer.dto.request.CustomerAddRequestDto;
import com.nishberkay.nishcustomer.dto.request.CustomerUpdateRequestDto;
import com.nishberkay.nishcustomer.dto.response.CustomerDto;
import com.nishberkay.nishcustomer.entity.mysqlentity.Customer;
import com.nishberkay.nishcustomer.exception.InvalidRequestException;
import com.nishberkay.nishcustomer.service.CustomerService;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
#RestController
#RequestMapping("/customer")
#Validated
public class CustomerController {
private CustomerService customerService;
private ModelMapper modelMapper;
#Autowired
public CustomerController(CustomerService customerService, ModelMapper modelMapper) {
this.customerService = customerService;
this.modelMapper = modelMapper;
}
#PutMapping
public CustomerDto updateCustomer(#Valid #RequestBody CustomerUpdateRequestDto customerDto,
BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
String errorMessage = bindingResult.getAllErrors().get(0).getDefaultMessage();
throw new InvalidRequestException(errorMessage);
}
Customer customer = modelMapper.map(customerDto, Customer.class);
return modelMapper.map(customerService.update(customer), CustomerDto.class);
}
}
And my dto is:
import javax.validation.constraints.NotNull;
#Data
public class CustomerUpdateRequestDto {
#NotNull
private int id;
#NotNull
private String firstName;
#NotNull
private String lastName;
}
My problem is (maybe #Valid is actually working I am not sure), when I debug it with the postman request:
{
"firstName" : "berkayaaasd",
"lastName" : "dada"
}
I am expecting some kind of message like "Id cannot be null" but id field is coming as 0 so thats why I think maybe #Valid is working but something else is wrong.
And here is the pom dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.8</version>
</dependency>
</dependencies>
The issue here is that you defined your id as a primitive type, which can never be null.
If you chose a boxed type (Integer in this case) this should work.
So in your DTO:
import javax.validation.constraints.NotNull;
#Data
public class CustomerUpdateRequestDto {
#NotNull
private Integer id;
...
}

Spring Boot Project - Issue with writing JPQL (Intellij)

So I have setup a spring boot project with which I'd like to create some specific queries.
Very simple, I just want to execute a select statement:
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public User findUserByEmail(String email) {
Query nativeQuery = entityManager.createQuery("select e from User");
nativeQuery.getFirstResult();
return new User();
}
}
In Intellij, when I hover over User it says cannot resolve symbol for User, I've had a google and most of the answers say you need to use the name of the entity, which of course I did:
#Entity(name = "PG_user")
public class User extends AbstractEntity {
#Column
private String email;
#Column
private String password;
public User(){}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Here is my POM also, if this helps:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>0.3.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>6.2.2.jre8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.webjars.bower/angular-bootstrap-contextmenu -->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>angular-bootstrap-contextmenu</artifactId>
<version>0.9.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.javax.persistence/hibernate-jpa-2.0-api -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
</dependencies>
But the name of your entity is PG_user
#Entity(name = "PG_user")
public class User extends AbstractEntity {
The name attribute defines the name of the entity that you must use for querying.
But I assume you want to set the name of the table.
That would be
#Table(name = "PG_user")
#Entity
public class User extends AbstractEntity {

404 not found in RestController in Spring Boot

I have a Spring Boot Project with pom.xml like this
<groupId>com.project</groupId>
<artifactId>prj</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>prj</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The Application class is like this
#SpringBootApplication
public class PrjApplication {
public static void main(String[] args) {
SpringApplication.run(HqApplication.class, args);
}
}
And I have a RestController where I am trying to do some CRUD Operations like this
#RestController("/register")
public class RegisterController {
#Autowired
private UserRepository userRepository;
#GetMapping("/user/{id}")
public User getUser(#Valid #RequestBody Long id){
User user = userRepository.getOne(id);
return user;
}
#PostMapping("/new")
public User saveUser(#Valid #RequestBody String name,#Valid #RequestBody String email,#Valid #RequestBody String password,#Valid #RequestBody String phone){
System.out.println("savginv user");
User user = new User();
user.setEmail(email);
user.setPassword(password);
user.setName(name);
user.setPhone(phone);
userRepository.save(user);
return user;
}
}
I have made an Entity class; User like this
#Entity
#Table(name = "tbl_users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String name, email, password, phone;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
When I try to save a user by calling the post method using PostMan it gives me 404 not found error.
{
"timestamp": "2018-06-23T06:47:14.086+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/prj/register/new"
}
I am using JPARepository to perform Database operations.
/prj isn't part of your controller. Either use a POST request without it (i.e., /register/new), or add it to your controller's mapping:
#RestController("/prj/register")
public class RegisterController {
// rest of the code (no pun intended)

JPA Cannot Deserialize Java 8 LocalDateTime

I'm using Spring Boot 1.5.1 and getting an exception anytime I hit my API when there is a LocalDateTime field in my Entity class.
The MySQL dt column is TIMESTAMP
Is JPA not able to natively deserialize LocalDateTime?
Console output when performing GET request
2017-03-02 22:00:18.797 ERROR 13736 --- [nio-8080-exec-1] 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.orm.jpa.JpaSystemException: could not deserialize; nested exception is org.hibernate.type.SerializationException: could not deserialize] with root cause
java.io.StreamCorruptedException: invalid stream header: 20323031
Reservation.class
package com.example.springboot.reservation;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
// Model class
#Entity
#Table(name="reservation")
public class Reservation {
#Id
private Long id;
#Column
private LocalDateTime dt;
#Column(name="user_id")
private Long userId;
// Hibernate will convert camel case column names to snake case!!!
// Don't use camelcase columns in DB
#Column(name="party_size")
private int partySize;
public Reservation() {}
public Reservation(Long id, Long userId, int partySize) {
this.id = id;
this.userId = userId;
this.partySize = partySize;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getDt() {
return dt;
}
public void setDt(LocalDateTime dt) {
this.dt = dt;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public int getPartySize() {
return partySize;
}
public void setPartySize(int partySize) {
this.partySize = partySize;
}
}
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
#Converter
public class LocalDateTimeConverter implements AttributeConverter<java.time.LocalDateTime, java.sql.Timestamp> {
#Override
public java.sql.Timestamp convertToDatabaseColumn(java.time.LocalDateTime entityValue) {
return entityValue == null ? null : java.sql.Timestamp.valueOf(entityValue)
}
#Override
public java.time.LocalDateTime convertToEntityAttribute(java.sql.Timestamp dbValue) {
return dbValue == null ? null : dbValue.toLocalDateTime();
}
}
Make sure that this converter class is added to the package scanned by hibernate. Add this converter to the column declaration
#Column
#Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime dt;
If you are not using JPA 2.0, This answer would help you use #Temporal annotation for LocalDateTime.
You don't need a converter if you use the newer hibernate-java8 which has LocalDateTime support.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>${hibernate.version}</version>
</dependency>
You can write converter as described below:
#Converter(autoApply = true)
public class MyLocalDateConverter implements AttributeConverter<java.time.LocalDate, java.sql.Date> {
#Override
public java.sql.Date convertToDatabaseColumn(java.time.LocalDate attribute) {
return attribute == null ? null : java.sql.Date.valueOf(attribute);
}
#Override
public java.time.LocalDate convertToEntityAttribute(java.sql.Date dbData) {
return dbData == null ? null : dbData.toLocalDate();
}
}
You can find some already baked converters in spring package:
org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters
JDocs:
JPA 2.1 converters to turn JSR-310 types into legacy Dates. To activate these converters make sure your persistence provider detects them by including this class in the list of mapped classes. In Spring environments, you can simply register the package of this class (i.e. org.springframework.data.jpa.convert.threeten) as package to be scanned on e.g. the LocalContainerEntityManagerFactoryBean.

How do I make Spring call a validator on a REST service?

I have studied many, many answers and I still can't see what I'm doing wrong.
I have a REST service controller which takes a bean as a parameter and I would like to validate that the fields on the bean are properly set.
The following is (a portion of) my configuration object.
#Configuration
#ComponentScan({...})
#EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
#Bean
public Validator validator() {
LocalValidatorFactoryBean validatorBean = new LocalValidatorFactoryBean();
return validatorBean;
}
#Override
public Validator getValidator() {
return validator();
}
}
I can't tell if those are necessary, but I can tell they're never called, so maybe they're in the wrong place?
The following is (a portion of) my pom.xml:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>5.1.1.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
I don't think el is necessary for what I'm doing, but I included it in case it made a difference.
Here is (a portion of) my bean class that needs to be validated:
public class ContactInfo {
#NotNull
#Size(min=1, max=50)
private String phone;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
And finally, this is (a portion of) the Rest Controller:
#RestController
#RequestMapping("/contact")
public class ContactController {
private static final String USER_ID_ATTRIBUTE = "userId";
#Autowired
private Validator validator;
#Autowired
#Qualifier("contactService")
private ContactService service;
#RequestMapping("/submit")
public void submitContact(
#Valid #RequestBody ContactInfo formData, BindingResult result) throws Exception {
assert(validator != null);
if(result.hasErrors()) {
throw new Exception("I don't like it one bit.");
}
service.submitContact(formData);
}
}
In the debugger, I can see that the validator passed in is an OptionalValidatorFactoryBean, and result.hasErrors() is always false.
The JSON passed in to the controller includes this:
{ "phone":"" }
I think that should trigger a validation error because it doesn't meet the minimum size.

Categories