User service is now non blocking

This commit is contained in:
Hubert Van De Walle 2020-06-15 20:57:09 +02:00
parent ebd897093c
commit 6688b35a9b
5 changed files with 92 additions and 70 deletions

View File

@ -6,3 +6,8 @@ import kotlinx.coroutines.*
fun <T> ioAsync(block: suspend CoroutineScope.() -> T): Deferred<T> { fun <T> ioAsync(block: suspend CoroutineScope.() -> T): Deferred<T> {
return CoroutineScope(Dispatchers.IO).async(block = block) return CoroutineScope(Dispatchers.IO).async(block = block)
} }
suspend inline fun <T> launchIo(crossinline block: () -> T): T =
withContext(Dispatchers.IO) {
block()
}

View File

@ -1,7 +1,10 @@
package be.vandewalleh.services package be.vandewalleh.services
import be.vandewalleh.entities.User import be.vandewalleh.entities.User
import be.vandewalleh.extensions.ioAsync
import be.vandewalleh.extensions.launchIo
import be.vandewalleh.tables.Users import be.vandewalleh.tables.Users
import kotlinx.coroutines.Deferred
import me.liuwj.ktorm.database.* import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.* import me.liuwj.ktorm.entity.*
@ -20,8 +23,8 @@ class UserService(override val kodein: Kodein) : KodeinAware {
/** /**
* returns a user ID if present or null * returns a user ID if present or null
*/ */
fun getUserId(userEmail: String): Int? { suspend fun getUserId(userEmail: String): Int? = launchIo {
return db.from(Users) db.from(Users)
.select(Users.id) .select(Users.id)
.where { Users.email eq userEmail } .where { Users.email eq userEmail }
.map { it[Users.id] } .map { it[Users.id] }
@ -31,8 +34,8 @@ class UserService(override val kodein: Kodein) : KodeinAware {
/** /**
* returns a user email and password from it's username if found or null * returns a user email and password from it's username if found or null
*/ */
fun getFromUsername(username: String): User? { suspend fun getFromUsername(username: String): User? = launchIo {
return db.from(Users) db.from(Users)
.select(Users.email, Users.password, Users.id) .select(Users.email, Users.password, Users.id)
.where { Users.username eq username } .where { Users.username eq username }
.map { row -> .map { row ->
@ -41,22 +44,22 @@ class UserService(override val kodein: Kodein) : KodeinAware {
.firstOrNull() .firstOrNull()
} }
fun userExists(username: String, email: String): Boolean { suspend fun userExists(username: String, email: String): Boolean = launchIo {
return db.from(Users) db.from(Users)
.select(Users.id) .select(Users.id)
.where { (Users.username eq username) or (Users.email eq email) } .where { (Users.username eq username) or (Users.email eq email) }
.firstOrNull() != null .firstOrNull() != null
} }
fun userExists(userId: Int): Boolean { suspend fun userExists(userId: Int): Boolean = launchIo {
return db.from(Users) db.from(Users)
.select(Users.id) .select(Users.id)
.where { Users.id eq userId } .where { Users.id eq userId }
.firstOrNull() != null .firstOrNull() != null
} }
fun getUserInfo(id: Int): User? { suspend fun getUserInfo(id: Int): User? = launchIo {
return db.from(Users) db.from(Users)
.select(Users.email, Users.username) .select(Users.email, Users.username)
.where { Users.id eq id } .where { Users.id eq id }
.map { Users.createEntity(it) } .map { Users.createEntity(it) }
@ -67,25 +70,27 @@ class UserService(override val kodein: Kodein) : KodeinAware {
* create a new user * create a new user
* password should already be hashed * password should already be hashed
*/ */
fun createUser(username: String, email: String, hashedPassword: String): User? { suspend fun createUser(username: String, email: String, hashedPassword: String): User? {
try { val newUser = User {
db.useTransaction { this.username = username
val newUser = User { this.email = email
this.username = username password = hashedPassword
this.email = email createdAt = LocalDateTime.now()
this.password = hashedPassword }
this.createdAt = LocalDateTime.now()
}
db.sequenceOf(Users).add(newUser) return try {
return newUser launchIo {
db.useTransaction {
db.sequenceOf(Users).add(newUser)
newUser
}
} }
} catch (e: SQLIntegrityConstraintViolationException) { } catch (e: SQLIntegrityConstraintViolationException) {
return null null
} }
} }
fun updateUser(userId: Int, username: String, email: String, hashedPassword: String) { suspend fun updateUser(userId: Int, username: String, email: String, hashedPassword: String): Unit = launchIo {
db.useTransaction { db.useTransaction {
db.update(Users) { db.update(Users) {
it.username to username it.username to username
@ -98,14 +103,17 @@ class UserService(override val kodein: Kodein) : KodeinAware {
} }
} }
fun deleteUser(userId: Int): Boolean { suspend fun deleteUser(userId: Int): Boolean = launchIo {
db.useTransaction { db.useTransaction {
return when (db.delete(Users) { it.id eq userId }) { db.delete(Users) { it.id eq userId }
1 -> true }
0 -> false }.let {
else -> error("??") when (it) {
} 1 -> true
0 -> false
else -> error("??")
} }
} }
} }

View File

@ -10,7 +10,8 @@ import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.algorithms.Algorithm
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.testing.* import io.ktor.server.testing.*
import io.mockk.every import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import org.amshove.kluent.* import org.amshove.kluent.*
@ -36,9 +37,9 @@ class AuthControllerKtTest {
} }
user["id"] = 1 user["id"] = 1
every { userService.getFromUsername("existing") } returns user coEvery { userService.getFromUsername("existing") } returns user
every { userService.userExists(1) } returns true coEvery { userService.userExists(1) } returns true
every { userService.getUserInfo(1) } returns User { coEvery { userService.getUserInfo(1) } returns User {
username = "existing" username = "existing"
email = "existing@mail.com" email = "existing@mail.com"
} }
@ -48,12 +49,12 @@ class AuthControllerKtTest {
username = "wrong" username = "wrong"
} }
user["id"] = 2 user["id"] = 2
every { userService.getFromUsername("wrong") } returns user2 coEvery { userService.getFromUsername("wrong") } returns user2
every { userService.getFromUsername("notExisting") } returns null coEvery { userService.getFromUsername("notExisting") } returns null
every { userService.userExists(3) } returns false coEvery { userService.userExists(3) } returns false
every { userService.getUserInfo(3) } returns null coEvery { userService.getUserInfo(3) } returns null
} }
@ -78,7 +79,7 @@ class AuthControllerKtTest {
} }
} }
verify { userService.getFromUsername("existing") } coVerify { userService.getFromUsername("existing") }
res.status() `should be equal to` HttpStatusCode.OK res.status() `should be equal to` HttpStatusCode.OK
val jsonObject = JSONObject(res.content) val jsonObject = JSONObject(res.content)
@ -106,7 +107,7 @@ class AuthControllerKtTest {
} }
} }
verify { userService.getFromUsername("wrong") } coVerify { userService.getFromUsername("wrong") }
res.status() `should be equal to` HttpStatusCode.Unauthorized res.status() `should be equal to` HttpStatusCode.Unauthorized
res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" res.content `should strictly be equal to json` """{msg: "Unauthorized"}"""
@ -121,7 +122,7 @@ class AuthControllerKtTest {
} }
} }
verify { userService.getFromUsername("notExisting") } coVerify { userService.getFromUsername("notExisting") }
res.status() `should be equal to` HttpStatusCode.Unauthorized res.status() `should be equal to` HttpStatusCode.Unauthorized
res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" res.content `should strictly be equal to json` """{msg: "Unauthorized"}"""
@ -154,7 +155,7 @@ class AuthControllerKtTest {
val jsonObject = JSONObject(res.content) val jsonObject = JSONObject(res.content)
jsonObject.keyList() `should be equal to` listOf("token", "refreshToken") jsonObject.keyList() `should be equal to` listOf("token", "refreshToken")
verify { userService.userExists(1) } coVerify { userService.userExists(1) }
res.status() `should be equal to` HttpStatusCode.OK res.status() `should be equal to` HttpStatusCode.OK
} }
@ -169,7 +170,7 @@ class AuthControllerKtTest {
} }
} }
verify { userService.userExists(3) } coVerify { userService.userExists(3) }
res.status() `should be equal to` HttpStatusCode.Unauthorized res.status() `should be equal to` HttpStatusCode.Unauthorized
res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" res.content `should strictly be equal to json` """{msg: "Unauthorized"}"""
} }

View File

@ -7,7 +7,7 @@ import be.vandewalleh.module
import be.vandewalleh.services.UserService import be.vandewalleh.services.UserService
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.testing.* import io.ktor.server.testing.*
import io.mockk.every import io.mockk.coEvery
import io.mockk.mockk import io.mockk.mockk
import org.amshove.kluent.* import org.amshove.kluent.*
import org.junit.jupiter.api.* import org.junit.jupiter.api.*
@ -24,31 +24,31 @@ class UserControllerKtTest {
init { init {
// new user // new user
every { userService.userExists("new", "new@test.com") } returns false coEvery { userService.userExists("new", "new@test.com") } returns false
every { userService.createUser("new", "new@test.com", any()) } returns User { coEvery { userService.createUser("new", "new@test.com", any()) } returns User {
this.createdAt = LocalDateTime.now() this.createdAt = LocalDateTime.now()
this.username = "new" this.username = "new"
this.email = "new@test.com" this.email = "new@test.com"
} }
// existing user // existing user
every { userService.userExists("existing", "existing@test.com") } returns true coEvery { userService.userExists("existing", "existing@test.com") } returns true
every { userService.createUser("existing", "existing@test.com", any()) } returns null coEvery { userService.createUser("existing", "existing@test.com", any()) } returns null
every { userService.getUserId("existing@test.com") } returns 1 coEvery { userService.getUserId("existing@test.com") } returns 1
every { userService.deleteUser(1) } returns true andThen false coEvery { userService.deleteUser(1) } returns true andThen false
// modified user // modified user
every { userService.userExists("modified", "modified@test.com") } returns true coEvery { userService.userExists("modified", "modified@test.com") } returns true
every { coEvery {
userService.userExists( userService.userExists(
and(not("modified"), not("existing")), and(not("modified"), not("existing")),
and(not("modified@test.com"), not("existing@test.com")) and(not("modified@test.com"), not("existing@test.com"))
) )
} returns false } returns false
every { userService.userExists(1) } returns true coEvery { userService.userExists(1) } returns true
every { userService.createUser("modified", "modified@test.com", any()) } returns null coEvery { userService.createUser("modified", "modified@test.com", any()) } returns null
every { userService.getUserId("modified@test.com") } returns 1 coEvery { userService.getUserId("modified@test.com") } returns 1
every { userService.updateUser(1, "ThisIsMyNewName", "ThisIsMyNewName@mail.com", any()) } returns Unit coEvery { userService.updateUser(1, "ThisIsMyNewName", "ThisIsMyNewName@mail.com", any()) } returns Unit
} }

View File

@ -3,6 +3,7 @@ package integration.services
import be.vandewalleh.mainModule import be.vandewalleh.mainModule
import be.vandewalleh.migrations.Migration import be.vandewalleh.migrations.Migration
import be.vandewalleh.services.UserService import be.vandewalleh.services.UserService
import kotlinx.coroutines.runBlocking
import org.amshove.kluent.* import org.amshove.kluent.*
import org.junit.jupiter.api.* import org.junit.jupiter.api.*
import org.kodein.di.Kodein import org.kodein.di.Kodein
@ -36,34 +37,41 @@ class UserServiceTest {
@Test @Test
@Order(1) @Order(1)
fun `test create user`() { fun `test create user`() {
val username = "hubert" runBlocking {
val email = "a@a" val username = "hubert"
val password = "password" val email = "a@a"
println(userService.createUser(username, email, password)) val password = "password"
val id = userService.getUserId(email) userService.createUser(username, email, password)
id `should not be` null val id = userService.getUserId(email)
id `should not be` null
userService.getUserInfo(id!!)!!.let { userService.getUserInfo(id!!)!!.let {
it.username `should be equal to` username it.username `should be equal to` username
it.email `should be equal to` email it.email `should be equal to` email
}
} }
} }
@Test @Test
@Order(2) @Order(2)
fun `test create same user`() { fun `test create same user`() {
userService.createUser(username = "hubert", hashedPassword = "password", email = "a@a") `should be` null runBlocking {
userService.createUser(username = "hubert", hashedPassword = "password", email = "a@a") `should be` null
}
} }
@Test @Test
@Order(3) @Order(3)
fun `test delete user`() { fun `test delete user`() {
val email = "a@a" runBlocking {
val id = userService.getUserId(email)!! val email = "a@a"
userService.deleteUser(id) val id = userService.getUserId(email)!!
userService.deleteUser(id)
userService.getUserId(email) `should be` null userService.getUserId(email) `should be` null
userService.getUserInfo(id) `should be` null userService.getUserInfo(id) `should be` null
}
} }
} }