Validating Spring #PathVariable in Kotlin Controller - spring

I'm attempting to validate a String #PathVariable inside a Kotlin Spring controller.
#RestController
#Validated
#RequestMapping("/kotlin/")
open class KStringController {
#GetMapping(path = ["string/{username}"],
produces = [APPLICATION_JSON_VALUE])
#ResponseBody
fun validateStringPathVariable(
#Pattern(regexp = "[A-Za-z]+", message = "Username Pattern Validation Message")
#Size(min = 2, max = 15, message = "Username Size Validation Message")
#PathVariable("username") username: String?
): ResponseEntity<String>? {
logger.info { String.format("validateStringPathVariable: Got Username [%s]", username) }
System.out.printf("validateStringPathVariable: Username is [%s]%n", username)
return ResponseEntity.ok("Username is valid")
}
}
I have unit tests for it:
#ExtendWith(SpringExtension::class)
#WebMvcTest(KStringController::class)
#AutoConfigureMockMvc
class KStringGetControllerTest {
#field:Autowired
private lateinit var mvc: MockMvc
#Test
#Throws(Exception::class)
fun validStringPathVariable() {
val request: MockHttpServletRequestBuilder = givenARequestFor("/kotlin/string/mike")
val actions: ResultActions = whenTheRequestIsMade(request)
thenExpect(actions,
MockMvcResultMatchers.status().isOk,
MockMvcResultMatchers.content().bytes("Username is valid".toByteArray()))
}
#Test
#Throws(Exception::class)
fun shoutsWhenStringPathVariableIsTooShort() {
val request = givenARequestFor("/kotlin/string/a")
val actions = whenTheRequestIsMade(request)
val response = """{
"validationErrors": [
{
"fieldName": "validateStringPathVariable.username",
"message": "Username Size Validation Message"
}
]
}"""
val content = MockMvcResultMatchers.content()
thenExpect(actions,
MockMvcResultMatchers.status().isBadRequest,
content.contentType(MediaType.APPLICATION_JSON),
content.json(response))
}
private fun givenARequestFor(url: String): MockHttpServletRequestBuilder {
return MockMvcRequestBuilders.get(url)
.characterEncoding("UTF-8")
}
#Throws(Exception::class)
private fun whenTheRequestIsMade(request: MockHttpServletRequestBuilder): ResultActions {
return mvc.perform(request)
}
#Throws(Exception::class)
private fun thenExpect(resultActions: ResultActions, vararg matchers: ResultMatcher) {
resultActions.andExpect(ResultMatcher.matchAll(*matchers))
}
Currently the second test is failing; it's returning a 200 response code when I'm expecting a 400. Much much more code at https://github.com/Mavelous/spring-validation.

With help from others, I've managed to find a solution.
Quick answer: Change the function to open fun validateStringPathVariable, and the validations will start working.
Alternative answer: Use the org.jetbrains.kotlin.plugin.spring plugin in your build.gradle file.
I also created a blog post with a longer description: http://mavelo.us/2021/04/07/spring-validation-in-kotlin.html

Related

How to get a data from mongoDB with the _id in Kotlin

I'm new in programming and I'm had having some problems to create an API for Kotlin getting data from a existing DB from mongoDB to my API.
I'm making Tests and I want to return a data with it Id, but I can't.
I'm using the SpringBoot, JPA, and mongoDB Data. The host is my machine, the database name is ArthurLeywin, and the collection name is Capitulos.
I'm receiving the error status=404, error=Not Found, path=/ArthueLeywin/Capitulos/6306e6417d5b2e259951f292}
The code of the Test is
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(SpringExtension::class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TbateMobileReederApplicationTests #Autowired constructor(
private val capituloRepository: CapituloRepository,
private val restTemplate: TestRestTemplate
) {
private val defaultCapituloId : ObjectId = ObjectId("6306e6417d5b2e259951f292")
//ObjectId.get()
#LocalServerPort
protected var port: Int = 27017
private fun getRootUrl(): String? = "http://localhost:$port/ArthueLeywin/Capitulos"
#Test
fun `should return single cap by id`() {
val response = restTemplate.getForObject(
getRootUrl() + "/$defaultCapituloId",
Object::class.java
)
val titleResponse = response.toString()
println(titleResponse)
}
}
My controller, if it's needed is
#RestController
#RequestMapping("/Capitulos")
class CapituloController (
private val capituloRepository : CapituloRepository
){
#GetMapping
fun getAllCapitulos() : ResponseEntity<List<Capitulo>>{
val capitulos = capituloRepository.findAll()
return ResponseEntity.ok(capitulos)
}
#GetMapping("/{id}")
fun getOneCapitulo(#PathVariable("id") id: String): ResponseEntity<Capitulo> {
val capitulo = capituloRepository.findOneById(ObjectId(id))
return ResponseEntity.ok(capitulo)
}
#PostMapping
fun createCapitulo (#RequestBody request: CapituloRequest) : ResponseEntity<Capitulo>{
val capitulo = capituloRepository.save(Capitulo(
nomeCapitulo = request.nomeCapitulo,
urlString = request.urlString,
novelTexto = request.novelTexto
))
return ResponseEntity(capitulo, HttpStatus.CREATED)
}
}
As I said I'm new in this, so please help me with explanations <3

Spring test #Transactional with #Nested junit classes

I am encountering a problem in spring transactions: even though I see in the output that a transaction is created and rolled back after each test method; when I check the database content, it still keeps the document that was saved during the transaction.
I ran mongodb instance in replica mode following instructions from here https://www.mongodb.com/docs/manual/tutorial/deploy-replica-set-for-testing/.
My test class is defined as follows:
#Transactional
#AutoConfigureMockMvc
#ActiveProfiles(profiles = ["test"])
#DisplayName("Login Functionality Test")
#SpringBootTest(classes = [TestConfiguration::class])
class LoginFunctionalityTest #Autowired constructor(
private val mockMvc: MockMvc,
private val userRepository: UserRepository,
private val openUserRepository: OpenUserRepository,
private val patientRepository: PatientRepository,
private val passwordEncoder: PasswordEncoder,
private val jwtMatcher: JWTMatcher,
#Value("\${secret:sD1fRUWtBdfA8BNcbf}") private val fastLoginSecret: String
) {
private val existingUsersPassword = "SOME_PASSWORD"
private val userFastLoginTokenSecret = "ANY_SECRET"
private lateinit var existingUser: User
private lateinit var existingOpenUser: OpenUser
private lateinit var existingPatient: Patient
#BeforeEach
fun prepareContext() {
println("Init db")
existingUser = userRepository.save(User(null, now(), "test", "test", "test#test.test", passwordEncoder.encode(existingUsersPassword), UserAuthority.Patient, true, true))
existingOpenUser = openUserRepository.save(OpenUser(null, "ANY", nextExpiryDate()))
existingPatient = patientRepository.save(Patient(null, existingUser))
}
#DisplayName("Login Controller")
#Nested
inner class LoginControllerTest{
#Test
fun `should return unauthorized 401 status code when user logins with not existing email`() {
val invalidEmail = "INVALID_EMAIL"
assertThrows<EmptyResultDataAccessException> { userRepository.findByEmail(invalidEmail) }
mockMvc.post("/auth/login") {
param("username", invalidEmail)
param("password", existingUsersPassword)
}.andExpect {
jsonPath("$.status", equalToIgnoringCase(HttpStatus.UNAUTHORIZED.name))
jsonPath("$.error",StringContains(true,"Invalid login credentials"))
jsonPath("$.data", nullValue())
status { isUnauthorized() }
}
}
...
}
#ActiveProfiles(profiles = ["open-user"])
#DisplayName("Open User Login Controller")
#Nested
inner class OpenUserControllerLoginTest(){
#Test
fun `should return bad request 400 status when any of the required login parameters are missing`(){
val validFastLoginToken = String(Base64.getEncoder().encode(passwordEncoder.encode("$userFastLoginTokenSecret${existingUser.passwordHash}$fastLoginSecret").toByteArray()))
mockMvc
.post("/auth/login/fast") {
param("userId", existingUser.id!!)
param("secret", userFastLoginTokenSecret)
param("fastLoginToken", validFastLoginToken)
}.andExpect {
jsonPath("$.data.isUserActivated", equalTo(existingUser.activated))
jsonPath("$.data.accessToken",jwtMatcher.matches(existingUser))
jsonPath("$.status", equalToIgnoringCase(HttpStatus.OK.name))
jsonPath("$.error", nullValue())
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON) }
}
}
...
}
The output in console has the "Init db" printed between creation and roll back of transaction.
It works correctly only if I move #ActiveProfiles from #Nested class to the outer class. Seems like #ActiveProfiles causes context reload but I don't know why this causes problems with transactions.

Spring boot validating #RequestParam not working in kotlin

I'm trying to validate text parameter to be not blank in spring boot kotlin project, but its not working correctly and returning just response code 200 instead of 422.
org.springframework.boot:spring-boot-starter-validation" is also present in classpath.
Controller.kt
#Validated
#RestController
#RequestMapping(ApiEndPoints.Search.SEARCH_ROOT_PATH)
class SearchController(
private val searchService: SearchService
) {
#GetMapping(ApiEndPoints.Search.BY_USERS)
fun searchUsers(
#Valid #NotBlank(message = "Text must not be blank") #RequestParam text: String,
#RequestParam(defaultValue = Constants.DEFAULT_PAGE_0) page: Int,
#RequestParam(defaultValue = Constants.DEFAULT_SIZE_15) size: Int
): ResponseEntity<ApiResponse.Success<Map<String, Any>>> {
val users = searchService.searchUsers(text, page, size)
return ResponseEntity.ok(ApiResponse.Success(true, users.createResponseMapFromSlice(Constants.USERS_LABEL)))
}
}
ControllerAdvice.kt
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
class GlobalExceptionHandler : ResponseEntityExceptionHandler() {
override fun handleMethodArgumentNotValid(
ex: MethodArgumentNotValidException,
headers: HttpHeaders,
status: HttpStatus,
request: WebRequest
): ResponseEntity<Any> {
val errors = hashMapOf<String, String>()
ex.bindingResult.allErrors.forEach {
val fieldName = (it as FieldError).field
val errorMessage = it.defaultMessage
errors[fieldName] = errorMessage.toString()
}
return ResponseEntity(
ApiResponse.ValidationError(
errors,
ErrorCodes.VALIDATION_FAILED
), HttpStatus.UNPROCESSABLE_ENTITY
)
}
}

Spring boot - FirebaseMessaging Service injection is not working in another service

I am trying to send firebase push notifications from a spring boot application.
Here I configured the FirebaseMessaging like:
#Configuration
class FirebaseConfig {
var logger: Logger = LoggerFactory.getLogger(FirebaseConfig::class.java)
#Value("\${app.firebase-config-file}")
lateinit var firebaseConfigPath: String
#Bean("firebaseMessaging")
#Throws(IOException::class)
fun firebaseMessaging(): FirebaseMessaging {
val firebaseOptions = FirebaseOptions
.builder()
.setCredentials(GoogleCredentials.fromStream(ClassPathResource(firebaseConfigPath).inputStream))
.build()
val app = FirebaseApp.initializeApp(firebaseOptions, "QInspect")
logger.info("Firebase application has been initialized")
return FirebaseMessaging.getInstance(app)
}
}
Here is the service I am using to send the push notifications:
#Service
class FirebaseNotificationService constructor(
#Autowired val firebaseMessaging: FirebaseMessaging,
#Autowired val mobileDeviceLogService: MobileDeviceLogService
) {
private var logger: Logger = LoggerFactory.getLogger(FirebaseNotificationService::class.java)
private val gson = Gson()
fun sendNotification(token: String, title: String, body: String): Status {
try {
val notification = Notification
.builder()
.setTitle(title)
.setBody(body)
.build()
val message = Message
.builder()
.setToken(token)
.setNotification(notification)
.build()
firebaseMessaging.send(message)
logger.info("Firebase Notification sent)
return Status(Constants.ResponseCode.SUCCESS, "Success")
} catch (e: Exception) {
logger.error("Firebase Notification Failed: ${e.printStackTrace()}")
}
return Status(Constants.ResponseCode.EXCEPTION, "Failed")
}
}
I can able to send the push notification using postman using the endpoint:
#RequestMapping("/notification")
#RestController
class NotificationResource constructor(#Autowired val firebaseService: FirebaseNotificationService) {
#RequestMapping(value = ["/message"], method = [RequestMethod.POST])
#ApiOperation(value = "Send firebase push notification with token", authorizations = [Authorization(value = "basic"), Authorization(value = "oauth2", scopes = [])])
#ResponseBody
#Throws(FirebaseMessagingException::class)
fun sendNotificationMsg(#RequestParam(value = "title", required = true) title: String, #RequestParam(value = "body", required = true) body: String): Status {
val user = SessionUtil.getUser()
return firebaseService.sendNotification(user.token, title, body)
}
Now the problem is when I am trying to inject the FirebaseNotificationService in another service to send the push notifications, But it's not working without any errors.
#Service
#Transactional
class MessageSubscriber {
#Autowired
var firebaseService: FirebaseNotificationService? = null
fun sendPush() {
firebaseService?.sendNotification("token", "Some Title", "Some body")
}
}
What I am missing here?
Update:
I got the issue, it's my mistake it is going inside the FirebaseNotificationService but failing to get the user token which I didn't mention. It was returning here.
val notificationToken =
tokenService.getTopByUserId(user.uuid!!)
?: return Status(Constants.ResponseCode.FAILED, "User or token not found")

How do we do kotlin data class validation on Collection?

I am trying to add validation to my data models in kotlin, The simple fields are easy to do using the #field annotations. But, I am struggling to do the same with collections.
I have uploaded the issue to github here
The java model is working with no issues but the kotlin version is not. I am adding both the models here.
public class JavaUser {
#NotEmpty
#NotNull
#Pattern(regexp = "[a-z]*", message = "Only lower case first name")
private String name;
private List<
#NotNull
#NotEmpty
#Pattern(regexp = "\\d{10}", message = "Only 10 digits")
String> phones;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getPhones() {
return phones;
}
public void setPhones(List<String> phones) {
this.phones = phones;
}
}
data class KotlinUser(
#field:NotEmpty
#field:NotNull
#field:Pattern(regexp = "[a-z]*", message = "Only lower case first name")
val name: String,
// Cannot use #field here, anything else we could use?
val phones: List<
#NotNull
#NotEmpty
#Pattern(regexp = "\\d{10}", message = "Only 10 digits")
String>
)
My tests - The java test passes but the kotlin one fails
#Test
fun `java user validation`() {
val javaUser = JavaUser()
javaUser.name = "sadfjsjdfhsjdf"
javaUser.phones = listOf("dfhgd")
webTestClient.put().uri("/user/java")
.body(BodyInserters.fromObject(javaUser))
.exchange()
.expectStatus().is4xxClientError
}
#Test
fun `kotlin user validation`() {
val kotlinUser = KotlinUser(name = "sadfjsjdfhsjdf", phones = listOf("dfhgd"))
webTestClient.put().uri("/user/kotlin")
.body(BodyInserters.fromObject(kotlinUser))
.exchange()
.expectStatus().is4xxClientError
}
Controller
#RestController
class Controller {
#PutMapping("/user/java")
fun putUser(#RequestBody #Valid javaUser: JavaUser): Mono<ResponseEntity<String>> =
Mono.just(ResponseEntity("shouldn't get this", HttpStatus.OK))
#PutMapping("/user/kotlin")
fun putUser(#RequestBody #Valid kotlinUser: KotlinUser): Mono<ResponseEntity<String>> =
Mono.just(ResponseEntity("shouldn't get this", HttpStatus.OK))
}
Any help would be greatly appreciated. Thank you!
This is currently not supported. The Kotlin compiler currently ignores annotations on types.
See for details:
https://discuss.kotlinlang.org/t/i-have-a-question-about-applying-bean-validation-2-0/5394
Kotlin data class and bean validation with container element constraints
There are also issues on the Kotlin issue tracker for this:
https://youtrack.jetbrains.net/issue/KT-26605
https://youtrack.jetbrains.net/issue/KT-13228
The latter has target version 1.4.
Try this:
data class KotlinUser(
#field:NotEmpty
#field:NotNull
#field:Pattern(regexp = "[a-z]*", message = "Only lower case first name")
val name: String,
#field:Valid
#field:NotEmpty
val phones: List<Phone>
)
data class Phone(
#NotBlank
#Pattern(regexp = "\\d{10}", message = "Only 10 digits")
val phoneNumber: String?
)
If you don't stick to Bean Validation, YAVI can be an alternative.
You can define the validation as follows:
import am.ik.yavi.builder.ValidatorBuilder
import am.ik.yavi.builder.forEach
import am.ik.yavi.builder.konstraint
data class KotlinUser(val name: String,
val phones: List<String>) {
companion object {
val validator = ValidatorBuilder.of<KotlinUser>()
.konstraint(KotlinUser::name) {
notEmpty()
.notNull()
.pattern("[a-z]*").message("Only lower case first name")
}
.forEach(KotlinUser::phones, {
constraint(String::toString, "value") {
it.notNull().notEmpty().pattern("\\d{10}").message("Only 10 digits")
}
})
.build()
}
}
Here is a YAVI version of your code.
A workaround there is to use the #Validated from spring in your controller.
Example:
#RestController
#RequestMapping("/path")
#Validated
class ExampleController {
#PostMapping
fun registerReturns(#RequestBody #Valid request: List<#Valid RequestClass>) {
println(request)
}
}
RequestClass:
#JsonIgnoreProperties(ignoreUnknown = true)
data class RequestClass(
#field:NotBlank
val field1: String?,
#field:NotBlank
#field:NotNull
val field2: String?)
This way Beans Validation is applied.

Resources