I want to update one variable for user.
Method:
fun blockUser(username: String): Boolean {
val user = userRepository.findUserByUsername(username)
if (user == null) {
throw UsernameNotFoundException("User not found by username: $username")
} else {
userRepository.blockUser(username)
}
val justToCheckIfUserIsUpdated = userRepository.findUserByUsername(username) //to be removed
return true
}
Query
#Modifying
#Transactional
#Query("update UserEntity c set c.isBlocked = TRUE WHERE c.username = :username")
fun blockUser(#Param("username") username: String)
Test:
#Test
fun `should block user when user exists`() {
//given
val userEntity = UserEntity(
username = "user",
phoneNumber = "500200300",
isBlocked = false,
email = "kupaa#wp.pl"
)
val userService = UserService(userDetailsRepository, userRepository, passwordEncoder)
userRepository.save(userEntity);
//when
userService.blockUser(userEntity.username);
//then
val blockedUser = userRepository.findUserByUsername(userEntity.username);
assertThat(blockedUser!!.isBlocked).isTrue()
}
While debugging you can see that isblocked is always true despite I updated it to true value.
Test ends with:
Expected :true
Actual :false
What I do wrong? I'm really confused.
When you do updates using JPQL, the updates go directly to the database. Hibernate doesn't magically update corresponding entities in the persistence context (the first level cache).
This userRepository.findUserByUsername(username) should get a user from the database (without the cache). But if you have a query cache enabled, the result can be loaded from the cache. You need to enable SQL logging and check the logs for that.
Also keep in mind that #Transactional above blockUser() method does nothing because spring boot creates a transaction for each test method. It doesn't mean that you have to remove #Transactional of course.
Related
I have this method
fun delete(id: Long) {
NotFoundExceptionValidator(!dishOfTheDayEntityRepository.existsById(id), "dishOfTheDay not found")
dishOfTheDayEntityRepository.deleteById(id)
}
NotFoundExceptionValidator this just checks if it's null then throws error
this is what I tried
#ConcurrentExecution
internal class DishOfTheDayServiceTest {
private val repo: DishOfTheDayEntityRepository = mockk()
private val mapper: DishOfTheDayMapper = mockk()
private val dishOfTheDayEntityService = DishOfTheDayService(repo, mapper)
#Test
fun `delete should work properly`() {
//given
val id: Long = 1;
//when
dishOfTheDayEntityService.delete(1)
//then
verify(exactly = 1) { repo.deleteById(1) }
}
}
when i run it it throws this error
no answer found for: DishOfTheDayEntityRepository(#1).existsById(1)
io.mockk.MockKException: no answer found for: DishOfTheDayEntityRepository(#1).existsById(1)
You forgot to mock your mocks behaviour, i.e. you should explicitly specify what the existsById() and deleteById() methods return. For example for existsById() it should look like:
every { repo.existsById(id) } returns true
I suppose that the deleteById() method returns Unit so if you don't want to do it like above you can mock DishOfTheDayEntityRepository like:
private val repo: DishOfTheDayEntityRepository = mockk(relaxUnitFun = true)
Now you don't have to mock Unit returning methods of DishOfTheDayEntityRepository. You can find more about it here.
I'm unit testing with mockito and I get nulls in the repository, after doing a .save().
My test:
....
#BeforeEach
void setUp() {
// USUARIO
user = new User();
user.setUserName("Username A");
user.setPassword("Pass A");
user.setConfirmPassword("Pass A");
}
#MockitoSettings(strictness = Strictness.LENIENT)
#Test
void createUserNoExistValidConfirmation() throws Exception {
User userToPassParam = new User();
userToPassParam.setUserName("Username_Aa");
userToPassParam.setPassword("Pass A");
userToPassParam.setConfirmPassword("Pass A");
// checkUserNameAvailable
Mockito.when(userDaoRepository.findByUserName(userToPassParam.getUserName())).thenReturn(Optional.ofNullable(user));
// -- checkPasswordValid
Mockito.when(bCryptPasswordEncoder.encode(userToPassParam.getPassword())).thenReturn(user.getPassword());
// -- save
Mockito.when(userDaoRepository.save(userToPassParam)).thenReturn(user);
User userToCallServiceImpl = new User();
userToCallServiceImpl.setUserName("Username A");
userToCallServiceImpl.setPassword("Pass A");
userToCallServiceImpl.setConfirmPassword("Pass A");
User user = userServiceImpl.createUser(userToCallServiceImpl); // HERE GET NULLS
System.out.println("User: " + user);
System.out.println("this.user: " + this.user);
Assertions.assertEquals(user.getUserName(), this.user.getUserName());
}
.....
value of userToPassParam passed parameter:
the expected object
I only treat 3 values of the object, "userName, password, confirmPassword" the other values do not matter to me.
When I try to pass the test: userServiceImpl
#Override
public User createUser(User user) throws Exception {
if (checkUserNameAvailable(user) && checkPasswordValid(user)) {
// cogemos el estado de la DB para más adelante: user = save()
String encodePassword = bCryptPasswordEncoder.encode(user.getPassword());
user.setPassword(encodePassword);
user = userDaoRepository.save(user);
}
return user;
}
The user has a "before save" value. Right when it saves I get a null.
After repository.save(object) the object returned by the .save "mock object" is null.
I don't understand very well why the .save returns null, when in the "when" I specify what to return.
It's as if mockito didn't intercept the .save() to return the object.
Note: I use Junit 5, Mockito 4.6.1
I am expecting the .save() to return the "user" as I specified in my #BeforeEach void setUp() {...}.
Mockito.when(userDaoRepository.save(userToPassParam)).thenReturn(user);
That is, the intercept of the .save() so that it returns the object.
I have fixed this with an ArgumentMatchers. For some unknown reason, Mockito needs it.
import static org.mockito.ArgumentMatchers.any;
Mockito.when(userDaoRepository.save(any(User.class))).thenReturn(user);
Information source: here
Note: I have also implemented equals and hashCode overrides, but it doesn't detect them.
We have successfully developed webosocket+stomp+rabbitmq for our project with security. It is working fine although we have some problems in solving the following case:
The workflow of this websocket works as following:
First user subscribes to the websocket endpoint which is working as expected
Second after being authorized by user's token, the user tries to subscribe the following endpoint '/user/queue/' + chatRoomId +
'.messages'. Here chatroomId defines to which chatroom user connects
to, which is also working fine, however here user can connect any
chatroomid which is not being validated in the backend which is also
the big matter we are trying to solve.
stompClient.subscribe('/user/queue/' + chatRoomId + '.messages', incomingMessages);
My question is how can i validate any users when they try to subscribe to this endpoint? I mean is there any way to handle each specific subscriptions
This is our front end code. if u need complete page i will upload it
function connect() {
socket = new SockJS('http://localhost:9600/wsss/messages');
stompClient = Stomp.over(socket);
// var stompClient = Stomp.client("ws://localhost:9600/ws/messages");
// stompClient.connect({ 'chat_id' : chatRoomId,
// 'X-Authorization' : 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0Iiwic2NvcGVzIjoiUk9MRV9BRE1JTiIsImVtYWlsIjoiYWRtaW5AZ21haWwuY29tIiwiaWF0IjoxNTc5MDgxMzg5LCJleHAiOjE1ODE2NzMzODl9.H3mnti0ZNtH6uLe-sOfrr5jzwssvGNcBiHGg-nUQ6xY' },
// stompSuccess, stompFailure);
stompClient.connect({ 'chatRoomId' : chatRoomId,
'login' : 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyODg5Iiwic2NvcGVzIjoiUk9MRV9VU0VSLFJPTEVfTU9ERVJBVE9SIiwiaWF0IjoxNTgyMDMxMDA0LCJleHAiOjE1ODQ2MjMwMDR9.NGAAed4R46FgrtgyDmrLSrmd-o3tkqbF60vOg8vAWYg' },
stompSuccess, stompFailure);
}
function stompSuccess(frame) {
enableInputMessage();
successMessage("Your WebSocket connection was successfuly established!");
console.log(frame);
stompClient.subscribe('/user/queue/' + chatRoomId + '.messages', incomingMessages);
stompClient.subscribe('/topic/notification', incomingNotificationMessage);
// stompClient.subscribe('/app/join/notification', incomingNotificationMessage);
}
And here is the code I am using for my backend
#Configuration
#EnableWebSocketMessageBroker
#Order(Ordered.HIGHEST_PRECEDENCE + 99)
class WebSocketConfig #Autowired constructor(
val jwtTokenUtil: TokenProvider
) : WebSocketMessageBrokerConfigurer {
#Autowired
#Resource(name = "userService")
private val userDetailsService: UserDetailsService? = null
#Autowired
private lateinit var authenticationManager: AuthenticationManager
#Value("\${spring.rabbitmq.username}")
private val userName: String? = null
#Value("\${spring.rabbitmq.password}")
private val password: String? = null
#Value("\${spring.rabbitmq.host}")
private val host: String? = null
#Value("\${spring.rabbitmq.port}")
private val port: Int = 0
#Value("\${endpoint}")
private val endpoint: String? = null
#Value("\${destination.prefix}")
private val destinationPrefix: String? = null
#Value("\${stomp.broker.relay}")
private val stompBrokerRelay: String? = null
override fun configureMessageBroker(config: MessageBrokerRegistry) {
config.enableStompBrokerRelay("/queue/", "/topic/")
.setRelayHost(host!!)
.setRelayPort(port)
.setSystemLogin(userName!!)
.setSystemPasscode(password!!)
config.setApplicationDestinationPrefixes(destinationPrefix!!)
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/websocket").setAllowedOrigins("*").setAllowedOrigins("*")
registry.addEndpoint("/websocket/messages").addInterceptors(customHttpSessionHandshakeInterceptor()).setAllowedOrigins("*")
registry.addEndpoint("/wsss/messages").addInterceptors(customHttpSessionHandshakeInterceptor()).setAllowedOrigins("*").withSockJS()
}
#Bean
fun customHttpSessionHandshakeInterceptor(): CustomHttpSessionHandshakeInterceptor {
return CustomHttpSessionHandshakeInterceptor()
}
override fun configureClientInboundChannel(registration: ChannelRegistration) {
registration.interceptors(object : ChannelInterceptor {
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*> {
val accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor::class.java)
if (StompCommand.CONNECT == accessor!!.command || StompCommand.STOMP == accessor.command) {
val authorization = accessor.getNativeHeader("login")
println("X-Authorization: {$authorization}")
val authToken = authorization!![0].split(" ")[1]
val username = jwtTokenUtil.getUsernameFromToken(authToken)
if (username != null) {
if(username.contains("#")) {
val userDetails = userDetailsService!!.loadUserByUsername(username)
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
val authentication = jwtTokenUtil.getAuthentication(authToken, SecurityContextHolder.getContext().authentication, userDetails)
accessor.user = authentication
}
} else {
val authorities = jwtTokenUtil.getAuthoritiesFromToken(authToken)
val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(username, "", authorities)
val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
accessor.user = authentication
}
}
}
return message
}
})
}
}
Here is the events handler
#Component
class WebSocketEvents {
#EventListener
fun handleSessionConnected(event: SessionConnectEvent) {
val headers = SimpMessageHeaderAccessor.wrap(event.message)
if ( headers.getNativeHeader("chatRoomId") != null && headers.getNativeHeader("chatRoomId")!!.isNotEmpty()){
val chatId = headers.getNativeHeader("chatRoomId")!![0]
if (headers.sessionAttributes != null)
headers.sessionAttributes!!["chatRoomId"] = chatId
}
}
#EventListener
fun handleSessionDisconnect(event: SessionDisconnectEvent) {
val headers = SimpMessageHeaderAccessor.wrap(event.message)
val chatRoomId = headers.sessionAttributes!!["chatRoomId"].toString()
}
}
So far what I have tried: As you can see above when user first connecting to the websocket endpoint http://localhost:9600/wsss/messages it is sending token and chatroom id (headers) and I am handling this in events listener component by resetting chatroomid into header attributes.
What I really need to do is take chatroom id while user subscribing to this specific destionation and apply validation whether he belongs to this chatroom and if so just give him permission | let him join the chat if not return error
I really appreciate any thought or workarounds!
I have spent couple of day searching for an answer but did not find any so I have figured out by myself. Here is my solution for this problem, though it is not complete one.
I have created separate interceptor class for handling all connection types as I was doing while catching subscribe command. So
it came to my mind, why not to use Subscribe command to listen users
actions and respond to it properly. For instance like this
#Component
class WebSocketTopicHandlerInterceptor constructor() : ChannelInterceptor {
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
val accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor::class.java)
if (StompCommand.CONNECT == accessor!!.command || StompCommand.STOMP == accessor.command) {
val authorization = accessor.getNativeHeader("login").apply { if (isNullOrEmpty()) throw LoggedError(AuthorizationException()) }
val authToken = authorization!![0].split(" ").apply { if (size <= 1) throw LoggedError(InvalidTokenException("Token is not valid")) }[1]
val username = jwtTokenUtil.getUsernameFromToken(authToken)
//DO YOUR AUTHENTICATION HERE
}
if (StompCommand.SUBSCRIBE == accessor.command) {
val destination = accessor.destination
if (destination.isNullOrBlank()) throw LoggedError(CustomBadRequestException("Subscription destionation cannot be null! U DUMB IDIOT!"))
val chatPattern = "/user/queue/+[a-zA-Z0-9-]+.messages".toRegex()
val notificationPattern = "/topic/notification".toRegex()
if (chatPattern.matches(accessor.destination!!)) println("working")
// FINDING OUT WHERE USER IS TRYING TO SUBSCRIBE ALL ROUTING LOGIC GOES HERE...
when {
chatPattern.matches(destination) -> {
//do your all logic here
}
notificationPattern.matches(destination) -> {
//do your all logic here
}
}
}
return message
}
}
IS THERE ANYTHING DOES NOT MAKE SENSE JUST LET ME KNOW I WILL BE VERY HAPPY TO ELABORATE ON ANYTHING FURTHER.
What I have done in my case is that I have figured out where the user is going and do my all validation there otherwise user cannot subscribe to any channel which means it is very secure.
Written a short convenicence extension for Testcontainers:
fun JdbcDatabaseContainer<*>.execute(query:DSLContext.()-> Query){
val connection = DriverManager.getConnection(this.getJdbcUrl(),this.getUsername(),this.getPassword())
val create = DSL.using(connection)
create.query().execute()
}
And now wanted to test it.
Flyway loads 30 entries. These should be visible in allDataPresent
canInsert inserts one entry without the extension
canInsertWithExtension does the same but via the extension function
insertMultipleWithExtension does exactly as its name implies and inserts another 5
All but the allDataPresent testcase (because that one is read-only anyway) are annotated #Transactional.
As such, I'd expect these modifications to be rolled back after the test method.
What instead happens is
[ERROR] Failures:
[ERROR] InitDataIT.allDataPresent:70
Expecting:
<36>
to be equal to:
<30>
but was not.
[ERROR] InitDataIT.canInsert:90
Expecting:
<6>
to be equal to:
<1>
but was not.
[ERROR] InitDataIT.canInsertWithExtension:112
Expecting:
<6>
to be equal to:
<1>
but was not.
Each #Test is working fine on its own. So the issue must lie with the #Transactional.
So why is that? And more importantly, how do I get the rollbacks?
Full testcase (also tried annotating the class instead, didn't make any difference):
#Testcontainers
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(initializers = [InitDataIT.TestContextInitializer::class])
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
open class InitDataIT {
companion object {
#JvmStatic
#Container
private val dbContainer = MySQLContainer<Nothing>().apply {
withDatabaseName("test")
withUsername("root")
withPassword("")
}
}
object TestContextInitializer: ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(applicationContext: ConfigurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=${dbContainer.jdbcUrl}",
"spring.datasource.username=${dbContainer.username}",
"spring.datasource.password=${dbContainer.password}",
"spring.datasource.driver-class-name=${dbContainer.driverClassName}"
).applyTo(applicationContext)
}
}
private val create:DSLContext
#Autowired
constructor(create:DSLContext){
this.create = create
}
#Test
fun allDataPresent(){
//given
val expectedNumberOfEntries = 30
val query = create.selectCount()
.from(CUSTOMERS)
//when
val numberOfEntries = query.fetchOne{it.value1()}
//then
Assertions.assertThat(numberOfEntries).isEqualTo(expectedNumberOfEntries)
}
#Test
#Transactional
open fun canInsert(){
//given
val insertquery = create.insertInto(CUSTOMERS)
.columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
.values("Alice","Tester","Alice.Tester#somewhere.tt",CustomerStatus.Contacted.name)
val expectedNumberInOffice2 = 1
//when
insertquery.execute()
//then
val numberInOffice2 = create.selectCount()
.from(CUSTOMERS)
.where(CUSTOMERS.EMAIL.contains("somewhere"))
.fetchOne{it.value1()}
assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)
}
#Test
#Transactional
open fun canInsertWithExtension(){
//given
dbContainer.execute {
insertInto(CUSTOMERS)
.columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
.values("Alice","Tester","Alice.Tester#somewhere.tt",CustomerStatus.Contacted.name)
}
val expectedNumberInOffice2 = 1
//when
val numberInOffice2 = create.selectCount()
.from(CUSTOMERS)
.where(CUSTOMERS.EMAIL.contains("somewhere"))
.fetchOne{it.value1()}
//then
assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)
}
#Test
#Transactional
open fun insertMultipleWithExtension(){
//given
dbContainer.execute {
insertInto(CUSTOMERS)
.columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
.values("Alice","Make","Alice.Make#somewhere.tt", CustomerStatus.Customer.name)
.values("Bob","Another","Bob.Another#somewhere.tt", CustomerStatus.ClosedLost.name)
.values("Charlie","Integration","Charlie.Integration#somewhere.tt",CustomerStatus.NotContacted.name)
.values("Denise","Test","Denise.Test#somewhere.tt",CustomerStatus.Customer.name)
.values("Ellie","Now","Ellie.Now#somewhere.tt",CustomerStatus.Contacted.name)
}
val expectedNumberInOffice2 = 5
//when
val numberInOffice2 = create.selectCount()
.from(CUSTOMERS)
.where(CUSTOMERS.EMAIL.contains("somewhere"))
.fetchOne{it.value1()}
//then
assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)
}
}
The Spring #Transactional annotation doesn't just magically work with your DriverManager created JDBC connections. Your dbContainer object should operate on your spring managed data source instead.
I have this strange problem in Spring Boot where #Cacheable is working in controller but not inside service. I can see GET call in Redis but not a PUT call.
This is working since it is inside controller
#RestController
#RequestMapping(value="/places")
public class PlacesController {
private AwesomeService awesomeService;
#Autowired
public PlacesController(AwesomeService awesomeService) {
this.awesomeService = awesomeService;
}
#GetMapping(value = "/search")
#Cacheable(value = "com.example.webservice.controller.PlacesController", key = "#query", unless = "#result != null")
public Result search(#RequestParam(value = "query") String query) {
return this.awesomeService.queryAutoComplete(query);
}
}
But #Cacheable is not working when I do this in Service like this
#Service
public class AwesomeApi {
private final RestTemplate restTemplate = new RestTemplate();
#Cacheable(value = "com.example.webservice.api.AwesomeApi", key = "#query", unless = "#result != null")
public ApiResult queryAutoComplete(String query) {
try {
return restTemplate.getForObject(query, ApiResult.class);
} catch (Throwable e) {
return null;
}
}
}
I can see the GET call in Redis but not a PUT call.
Your caching should work fine as it is. Make sure that you have the #EnableCaching annotation and that your unless criteria is correct.
Right now, you're using unless="#result != null", which means it will cache the result, unless it's not null. This means that it will almost never cache, unless the restTemplate.getForObject() returns null, or when an exception occurs, because then you're also returning null.
I'm assuming that you want to cache each value, except null, but in that case you have to inverse your condition, e.g.:
#Cacheable(
value = "com.example.webservice.api.AwesomeApi",
key = "#query",
unless = "#result == null") // Change '!=' into '=='
Or, as mentioned in the comments, in stead of reversing the condition, you can use condition in stead of unless:
#Cacheable(
value = "com.example.webservice.api.AwesomeApi",
key = "#query",
condition = "#result != null") // Change 'unless' into 'condition'