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

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

Related

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
)
}
}

Using ConnectableFlux for hot stream REST endpoint

I'm trying to create a REST endpoint to subscribe to a hot stream using Reactor.
My test provider for the stream looks like this:
#Service
class TestProvider {
fun getStream(resourceId: String): ConnectableFlux<QData> {
return Flux.create<QData> {
for (i in 1..10) {
it.next(QData(LocalDateTime.now().toString(), "next"))
Thread.sleep(500L)
}
it.complete()
}.publish()
}
}
My service for the REST endpoint looks like this:
#Service
class DataService #Autowired constructor(
private val prv: TestProvider
) {
private val streams = mutableMapOf<String, ConnectableFlux<QData>>()
fun subscribe(resourceId: String): Flux<QData> {
val stream = getStream(resourceId)
return Flux.push { flux ->
stream.subscribe{
flux.next(it)
}
flux.complete()
}
}
private fun getStream(resourceId: String): ConnectableFlux<QData> {
if(streams.containsKey(resourceId).not()) {
streams.put(resourceId, createStream(resourceId))
}
return streams.get(resourceId)!!
}
private fun createStream(resourceId: String): ConnectableFlux<QData> {
val stream = prv.getStream(resourceId)
stream.connect()
return stream
}
}
The controller looks like this:
#RestController
class DataController #Autowired constructor(
private val dataService: DataService
): DataApi {
override fun subscribe(resourceId: String): Flux<QData> {
return dataService.subscribe(resourceId)
}
}
The API interface looks like this:
interface DataApi {
#ApiResponses(value = [
ApiResponse(responseCode = "202", description = "Client is subscribed", content = [
Content(mediaType = "application/json", array = ArraySchema(schema = Schema(implementation = QData::class)))
])
])
#GetMapping(path = ["/subscription/{resourceId}"], produces = [MediaType.APPLICATION_JSON_VALUE])
fun subscribe(
#Parameter(description = "The resource id for which quality data is subscribed for", required = true, example = "example",allowEmptyValue = false)
#PathVariable("resourceId", required = true) #NotEmpty resourceId: String
): Flux<QData>
}
Unfortunately, my curl delivers an empty array.
Does anyone has an idea what the issue is? Thanks in advance!
I had to run connect() async in DataService:
CompletableFuture.runAsync {
stream.connect()
}

How do I mock LocalDateTime() in Kotlin with Mockk

I'm writing test for a UserService that creates an anonymous user, and part of that includes saving a timestamp at time of creation. The time seems to be mocked properly within the test function itself, but when a timestamp is added in the actual UserService, the real time is being returned. How do I mock it properly?
the relevant UserService function is:
UserService.kt
fun createAnonymousUser(jwt: AuthenticationJsonWebToken): User {
val user = User()
user.anonUserId = jwt.name
user.createdAt = LocalDateTime.now(ZoneOffset.UTC)
return userRepository.save(user)
}
UserService.test.kt
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
open class UserServiceTest {
private val jwt = mockk<AuthenticationJsonWebToken>()
private val userRepository = mockk<UserRepository>()
private val authProperties = AllAuthProperties()
private val fixedClock = mockk<LocalDateTime>()
private val slotUser = slot<User>()
#BeforeTest
internal fun init() {
clearAllMocks()
}
#Test
fun getMe() {
every { jwt.name } returns "anonymous|123"
every { LocalDateTime.now() } returns LocalDateTime.MAX
val userService = UserService(userRepository, authProperties)
val user = User();
user.anonUserId = jwt.name
user.createdAt = LocalDateTime.now()
every { userRepository.save(capture(slotUser)) } returns user
val anonUser = userService.getMe(jwt)
verify { userRepository.save(anonUser) }
assertEquals(anonUser, user)
}
}
I'm just not sure how to accomplish what I'm trying.
Turns out I had it mostly right; I just needed a slight tweak.
verify { userRepository.save(anonUser.capture) }
assertEquals(anonUser, user)

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