More tests !
This commit is contained in:
parent
913e3dfc93
commit
07ec732c55
17
api/pom.xml
17
api/pom.xml
@ -129,6 +129,11 @@
|
|||||||
<artifactId>ktorm-support-mysql</artifactId>
|
<artifactId>ktorm-support-mysql</artifactId>
|
||||||
<version>${ktorm_version}</version>
|
<version>${ktorm_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.liuwj.ktorm</groupId>
|
||||||
|
<artifactId>ktorm-jackson</artifactId>
|
||||||
|
<version>${ktorm_version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.hekeki</groupId>
|
<groupId>com.github.hekeki</groupId>
|
||||||
<artifactId>huckleberry</artifactId>
|
<artifactId>huckleberry</artifactId>
|
||||||
@ -179,6 +184,18 @@
|
|||||||
<version>1.61</version>
|
<version>1.61</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.skyscreamer</groupId>
|
||||||
|
<artifactId>jsonassert</artifactId>
|
||||||
|
<version>1.5.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.mockk</groupId>
|
||||||
|
<artifactId>mockk</artifactId>
|
||||||
|
<version>1.10.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<sourceDirectory>${project.basedir}/src</sourceDirectory>
|
<sourceDirectory>${project.basedir}/src</sourceDirectory>
|
||||||
|
|||||||
@ -12,4 +12,4 @@ interface User : Entity<User> {
|
|||||||
var password: String
|
var password: String
|
||||||
var createdAt: LocalDateTime
|
var createdAt: LocalDateTime
|
||||||
var lastLogin: LocalDateTime?
|
var lastLogin: LocalDateTime?
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
package be.vandewalleh.extensions
|
package be.vandewalleh.extensions
|
||||||
|
|
||||||
import be.vandewalleh.auth.UserDbIdPrincipal
|
import be.vandewalleh.auth.UserDbIdPrincipal
|
||||||
import be.vandewalleh.kodein
|
|
||||||
import be.vandewalleh.services.FullNoteCreateDTO
|
import be.vandewalleh.services.FullNoteCreateDTO
|
||||||
import be.vandewalleh.services.FullNotePatchDTO
|
import be.vandewalleh.services.FullNotePatchDTO
|
||||||
import be.vandewalleh.services.UserService
|
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.auth.*
|
import io.ktor.auth.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.request.*
|
import io.ktor.request.*
|
||||||
import io.ktor.response.*
|
import io.ktor.response.*
|
||||||
import org.kodein.di.generic.instance
|
|
||||||
|
|
||||||
suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) {
|
suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) {
|
||||||
respond(status, status.description)
|
respond(status, """{"msg": "${status.description}"}""")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -3,9 +3,12 @@ package be.vandewalleh.features
|
|||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.features.*
|
import io.ktor.features.*
|
||||||
import io.ktor.jackson.*
|
import io.ktor.jackson.*
|
||||||
|
import me.liuwj.ktorm.jackson.*
|
||||||
|
|
||||||
fun Application.contentNegotiationFeature() {
|
fun Application.contentNegotiationFeature() {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
jackson {}
|
jackson {
|
||||||
|
registerModule(KtormModule())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing
|
||||||
|
|
||||||
|
import be.vandewalleh.entities.User
|
||||||
import be.vandewalleh.extensions.respondStatus
|
import be.vandewalleh.extensions.respondStatus
|
||||||
import be.vandewalleh.extensions.userId
|
import be.vandewalleh.extensions.userId
|
||||||
import be.vandewalleh.services.UserService
|
import be.vandewalleh.services.UserService
|
||||||
@ -12,16 +13,22 @@ import io.ktor.routing.*
|
|||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
import org.mindrot.jbcrypt.BCrypt
|
import org.mindrot.jbcrypt.BCrypt
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
fun Routing.user(kodein: Kodein) {
|
fun Routing.user(kodein: Kodein) {
|
||||||
val userService by kodein.instance<UserService>()
|
val userService by kodein.instance<UserService>()
|
||||||
|
|
||||||
|
post("/user/test") {
|
||||||
|
val user = call.receive<User>()
|
||||||
|
call.respond(user)
|
||||||
|
}
|
||||||
|
|
||||||
route("/user") {
|
route("/user") {
|
||||||
post {
|
post {
|
||||||
val user = call.receive<UserDto>()
|
val user = call.receive<User>()
|
||||||
|
|
||||||
if (userService.userExists(user.username, user.email))
|
if (userService.userExists(user.username, user.email))
|
||||||
return@post call.respond(HttpStatusCode.Conflict)
|
return@post call.respondStatus(HttpStatusCode.Conflict)
|
||||||
|
|
||||||
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
|
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
|
||||||
|
|
||||||
@ -32,7 +39,7 @@ fun Routing.user(kodein: Kodein) {
|
|||||||
|
|
||||||
authenticate {
|
authenticate {
|
||||||
put {
|
put {
|
||||||
val user = call.receive<UserDto>()
|
val user = call.receive<User>()
|
||||||
|
|
||||||
if (userService.userExists(user.username, user.email))
|
if (userService.userExists(user.username, user.email))
|
||||||
return@put call.respond(HttpStatusCode.Conflict)
|
return@put call.respond(HttpStatusCode.Conflict)
|
||||||
@ -45,12 +52,13 @@ fun Routing.user(kodein: Kodein) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete {
|
delete {
|
||||||
userService.deleteUser(call.userId())
|
val status = if (userService.deleteUser(call.userId()))
|
||||||
call.respondStatus(HttpStatusCode.OK)
|
HttpStatusCode.OK
|
||||||
|
else
|
||||||
|
HttpStatusCode.NotFound
|
||||||
|
call.respondStatus(status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class UserDto(val username: String, val email: String, val password: String)
|
|
||||||
|
|||||||
@ -98,9 +98,13 @@ class UserService(override val kodein: Kodein) : KodeinAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteUser(userId: Int) {
|
fun deleteUser(userId: Int): Boolean {
|
||||||
db.useTransaction {
|
db.useTransaction {
|
||||||
db.delete(Users) { it.id eq userId }
|
return when (db.delete(Users) { it.id eq userId }) {
|
||||||
|
1 -> true
|
||||||
|
0 -> false
|
||||||
|
else -> error("??")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
142
api/test/routing/UserControllerKtTest.kt
Normal file
142
api/test/routing/UserControllerKtTest.kt
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package routing
|
||||||
|
|
||||||
|
import be.vandewalleh.auth.SimpleJWT
|
||||||
|
import be.vandewalleh.entities.User
|
||||||
|
import be.vandewalleh.mainModule
|
||||||
|
import be.vandewalleh.module
|
||||||
|
import be.vandewalleh.services.UserService
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.testing.*
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.amshove.kluent.*
|
||||||
|
import org.junit.jupiter.api.*
|
||||||
|
import org.kodein.di.Kodein
|
||||||
|
import org.kodein.di.generic.bind
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import utils.*
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
class UserControllerKtTest {
|
||||||
|
|
||||||
|
private val userService = mockk<UserService>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
// new user
|
||||||
|
every { userService.userExists("new", "new@test.com") } returns false
|
||||||
|
every { userService.createUser("new", "new@test.com", any()) } returns User {
|
||||||
|
this.createdAt = LocalDateTime.now()
|
||||||
|
this.username = "new"
|
||||||
|
this.email = "new@test.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
// existing user
|
||||||
|
every { userService.userExists("existing", "existing@test.com") } returns true
|
||||||
|
every { userService.createUser("existing", "existing@test.com", any()) } returns null
|
||||||
|
every { userService.getUserId("existing@test.com") } returns 1
|
||||||
|
every { userService.deleteUser(1) } returns true andThen false
|
||||||
|
|
||||||
|
// modified user
|
||||||
|
every { userService.userExists("modified", "modified@test.com") } returns true
|
||||||
|
every {
|
||||||
|
userService.userExists(
|
||||||
|
and(not("modified"), not("existing")),
|
||||||
|
and(not("modified@test.com"), not("existing@test.com"))
|
||||||
|
)
|
||||||
|
} returns false
|
||||||
|
every { userService.userExists(1) } returns true
|
||||||
|
every { userService.createUser("modified", "modified@test.com", any()) } returns null
|
||||||
|
every { userService.getUserId("modified@test.com") } returns 1
|
||||||
|
every { userService.updateUser(1, "ThisIsMyNewName", "ThisIsMyNewName@mail.com", any()) } returns Unit
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val kodein = Kodein {
|
||||||
|
import(mainModule, allowOverride = true)
|
||||||
|
bind<UserService>(overrides = true) with instance(userService)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val testEngine = TestApplicationEngine().apply {
|
||||||
|
start()
|
||||||
|
application.module(kodein)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class CreateUser {
|
||||||
|
@Test
|
||||||
|
fun `create a new user`() {
|
||||||
|
val res = testEngine.post("/user") {
|
||||||
|
json {
|
||||||
|
it["username"] = "new"
|
||||||
|
it["password"] = "test"
|
||||||
|
it["email"] = "new@test.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.status() `should be equal to` HttpStatusCode.Created
|
||||||
|
res.content `should be equal to json` """{msg:"Created"}"""
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create an existing user`() {
|
||||||
|
val res = testEngine.post("/user") {
|
||||||
|
json {
|
||||||
|
it["username"] = "existing"
|
||||||
|
it["email"] = "existing@test.com"
|
||||||
|
it["password"] = "test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.status() `should be equal to` HttpStatusCode.Conflict
|
||||||
|
res.content `should be equal to json` """{msg:"Conflict"}"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class DeleteUser {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `delete an existing user`() {
|
||||||
|
val authJwt by kodein.instance<SimpleJWT>("auth")
|
||||||
|
val token = authJwt.sign(1)
|
||||||
|
|
||||||
|
val res = testEngine.delete("/user") {
|
||||||
|
addHeader(HttpHeaders.Authorization, "Bearer $token")
|
||||||
|
}
|
||||||
|
res.status() `should be equal to` HttpStatusCode.OK
|
||||||
|
res.content `should be equal to json` """{msg:"OK"}"""
|
||||||
|
|
||||||
|
// try again
|
||||||
|
val res2 = testEngine.delete("/user") {
|
||||||
|
setToken(token)
|
||||||
|
}
|
||||||
|
res2.status() `should be equal to` HttpStatusCode.NotFound
|
||||||
|
res2.content `should be equal to json` """{msg:"Not Found"}"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class ModifyUser {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `modify a user`() {
|
||||||
|
val authJwt by kodein.instance<SimpleJWT>("auth")
|
||||||
|
val token = authJwt.sign(1)
|
||||||
|
|
||||||
|
val res = testEngine.put("/user") {
|
||||||
|
setToken(token)
|
||||||
|
json {
|
||||||
|
it["username"] = "ThisIsMyNewName"
|
||||||
|
it["email"] = "ThisIsMyNewName@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status() `should be equal to` HttpStatusCode.OK
|
||||||
|
res.content `should be equal to json` """{msg:"OK"}"""
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
19
api/test/utils/Assertions.kt
Normal file
19
api/test/utils/Assertions.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import org.skyscreamer.jsonassert.JSONAssert
|
||||||
|
|
||||||
|
infix fun String?.shouldBeEqualToJson(expected: String?) = JSONAssert.assertEquals(expected, this, false)
|
||||||
|
|
||||||
|
infix fun String?.`should be equal to json`(expected: String?) = shouldBeEqualToJson(expected)
|
||||||
|
|
||||||
|
infix fun String?.shouldStrictlyBeEqualToJson(expected: String?) = JSONAssert.assertEquals(expected, this, true)
|
||||||
|
|
||||||
|
infix fun String?.`should strictly be equal to json`(expected: String?) = shouldStrictlyBeEqualToJson(expected)
|
||||||
|
|
||||||
|
infix fun String?.shouldNotStrictlyBeEqualToJson(expected: String?) = JSONAssert.assertNotEquals(expected, this, true)
|
||||||
|
|
||||||
|
infix fun String?.`should not strictly be equal to json`(expected: String?) = shouldNotStrictlyBeEqualToJson(expected)
|
||||||
|
|
||||||
|
infix fun String?.shouldNotBeEqualToJson(expected: String?) = JSONAssert.assertNotEquals(expected, this, false)
|
||||||
|
|
||||||
|
infix fun String?.`should not be equal to json`(expected: String?) = shouldNotBeEqualToJson(expected)
|
||||||
23
api/test/utils/JsonAssertExtensions.kt
Normal file
23
api/test/utils/JsonAssertExtensions.kt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
operator fun JSONObject.set(name: String, value: String) {
|
||||||
|
this.put(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun JSONObject.set(name: String, value: Double) {
|
||||||
|
this.put(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun JSONObject.set(name: String, value: Long) {
|
||||||
|
this.put(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun JSONObject.set(name: String, value: Int) {
|
||||||
|
this.put(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun JSONObject.set(name: String, value: Boolean) {
|
||||||
|
this.put(name, value)
|
||||||
|
}
|
||||||
52
api/test/utils/KtorTestingExtensions.kt
Normal file
52
api/test/utils/KtorTestingExtensions.kt
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.server.testing.*
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
||||||
|
fun TestApplicationRequest.json(block: (JSONObject) -> Unit) {
|
||||||
|
addHeader(HttpHeaders.ContentType, "application/json")
|
||||||
|
setBody(JSONObject().apply(block).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TestApplicationRequest.setToken(token: String) {
|
||||||
|
addHeader(HttpHeaders.Authorization, "Bearer $token")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TestApplicationEngine.post(
|
||||||
|
uri: String,
|
||||||
|
setup: TestApplicationRequest.() -> Unit = {}
|
||||||
|
): TestApplicationResponse = handleRequest {
|
||||||
|
this.uri = uri
|
||||||
|
this.method = HttpMethod.Post
|
||||||
|
setup()
|
||||||
|
}.response
|
||||||
|
|
||||||
|
fun TestApplicationEngine.get(
|
||||||
|
uri: String,
|
||||||
|
setup: TestApplicationRequest.() -> Unit = {}
|
||||||
|
): TestApplicationResponse = handleRequest {
|
||||||
|
this.uri = uri
|
||||||
|
this.method = HttpMethod.Get
|
||||||
|
setup()
|
||||||
|
}.response
|
||||||
|
|
||||||
|
fun TestApplicationEngine.delete(
|
||||||
|
uri: String,
|
||||||
|
setup: TestApplicationRequest.() -> Unit = {}
|
||||||
|
): TestApplicationResponse = handleRequest {
|
||||||
|
this.uri = uri
|
||||||
|
this.method = HttpMethod.Delete
|
||||||
|
setup()
|
||||||
|
}.response
|
||||||
|
|
||||||
|
fun TestApplicationEngine.put(
|
||||||
|
uri: String,
|
||||||
|
setup: TestApplicationRequest.() -> Unit = {}
|
||||||
|
): TestApplicationResponse = handleRequest {
|
||||||
|
this.uri = uri
|
||||||
|
this.method = HttpMethod.Put
|
||||||
|
setup()
|
||||||
|
}.response
|
||||||
Loading…
x
Reference in New Issue
Block a user