package integration.routing import be.vandewalleh.auth.SimpleJWT import be.vandewalleh.entities.User import be.vandewalleh.features.Config import be.vandewalleh.mainModule import be.vandewalleh.module import be.vandewalleh.services.UserService import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import io.ktor.http.* import io.ktor.server.testing.* import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk import org.amshove.kluent.* import org.json.JSONObject import org.junit.jupiter.api.* import org.kodein.di.Kodein import org.kodein.di.generic.bind import org.kodein.di.generic.instance import org.mindrot.jbcrypt.BCrypt import utils.* import java.util.* @TestInstance(TestInstance.Lifecycle.PER_CLASS) class AuthControllerKtTest { private val userService = mockk() init { val user = User { password = BCrypt.hashpw("password", BCrypt.gensalt()) username = "existing" } user["id"] = 1 coEvery { userService.find("existing") } returns user coEvery { userService.exists(1) } returns true coEvery { userService.find(1) } returns User { username = "existing" } val user2 = User { password = BCrypt.hashpw("right password", BCrypt.gensalt()) username = "wrong" } user["id"] = 2 coEvery { userService.find("wrong") } returns user2 coEvery { userService.find("notExisting") } returns null coEvery { userService.exists(3) } returns false coEvery { userService.find(3) } returns null } private val kodein = Kodein { import(mainModule, allowOverride = true) bind(overrides = true) with instance(userService) } private val testEngine = TestApplicationEngine().apply { start() application.module(kodein) } @Nested inner class Login { @Test fun `login existing user with valid password`() { val res = testEngine.post("/user/login") { json { it["username"] = "existing" it["password"] = "password" } } coVerify { userService.find("existing") } res.status() `should be equal to` HttpStatusCode.OK val jsonObject = JSONObject(res.content) val hasToken = jsonObject.has("token") hasToken `should be equal to` true jsonObject.keyList() `should be equal to` listOf("token", "refreshToken") val authJwt by kodein.instance(tag = "auth") val token = jsonObject.getString("token") authJwt.verifier.verify(token) val refreshJwt by kodein.instance(tag = "refresh") val refreshToken = jsonObject.getString("refreshToken") refreshJwt.verifier.verify(refreshToken) } @Test fun `login existing user with invalid password`() { val res = testEngine.post("/user/login") { json { it["username"] = "wrong" it["password"] = "not this" } } coVerify { userService.find("wrong") } res.status() `should be equal to` HttpStatusCode.Unauthorized res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" } @Test fun `login not existing user`() { val res = testEngine.post("/user/login") { json { it["username"] = "notExisting" it["password"] = "babababa" } } coVerify { userService.find("notExisting") } res.status() `should be equal to` HttpStatusCode.Unauthorized res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" } @Test fun `login without body`() { val res = testEngine.post("/user/login") { addHeader(HttpHeaders.ContentType, "application/json") } res.status() `should be equal to` HttpStatusCode.BadRequest } } @Nested inner class Refresh { @Test fun `test valid refresh token`() { val refreshJwt by kodein.instance(tag = "refresh") val refreshToken = refreshJwt.sign(1) val res = testEngine.post("/user/refresh_token") { json { it["refreshToken"] = refreshToken } } val jsonObject = JSONObject(res.content) jsonObject.keyList() `should be equal to` listOf("token", "refreshToken") coVerify { userService.exists(1) } res.status() `should be equal to` HttpStatusCode.OK } @Test fun `test valid refresh token for deleted user`() { val refreshJwt by kodein.instance(tag = "refresh") val refreshToken = refreshJwt.sign(3) val res = testEngine.post("/user/refresh_token") { json { it["refreshToken"] = refreshToken } } coVerify { userService.exists(3) } res.status() `should be equal to` HttpStatusCode.Unauthorized res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" } @Test fun `test expired refresh token for existing user`() { val config by kodein.instance() val algorithm = Algorithm.HMAC256(config.jwt.refresh.secret.value) val expiredToken = JWT.create() .withClaim("id", 1) .withExpiresAt(Date(0)) // January 1, 1970, 00:00:00 GMT .sign(algorithm) val res = testEngine.post("/user/refresh_token") { json { it["refreshToken"] = expiredToken } } res.status() `should be equal to` HttpStatusCode.Unauthorized res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" } } @Nested inner class UserInfo { @Test fun `test user info for existing user`() { val authJwt by kodein.instance(tag = "auth") val token = authJwt.sign(1) val res = testEngine.get("/user/me") { setToken(token) } res.content `should strictly be equal to json` """{user:{username:"existing"}}""" res.status() `should be equal to` HttpStatusCode.OK } @Test fun `test user info on deleted user`() { val authJwt by kodein.instance(tag = "auth") val token = authJwt.sign(3) val res = testEngine.get("/user/me") { setToken(token) } res.status()!!.value `should not be in range` (200..299) val jsonObject = JSONObject(res.content) jsonObject.keyList() `should be equal to` listOf("msg") } } }