Remove nuxt + 100 other things..

This commit is contained in:
2020-07-08 19:46:04 +02:00
parent 3b80ae051d
commit 44b463d9d5
132 changed files with 6202 additions and 10961 deletions
@@ -0,0 +1,102 @@
package integration.routing
import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.entities.User
import be.vandewalleh.mainModule
import be.vandewalleh.module
import be.vandewalleh.repositories.UserRepository
import io.ktor.http.*
import io.ktor.server.testing.*
import io.mockk.coEvery
import io.mockk.mockk
import org.amshove.kluent.*
import org.junit.jupiter.api.*
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.instance
import utils.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ApiUserControllerKtTest {
private val userService = mockk<UserRepository>()
init {
// new user
coEvery { userService.exists("new") } returns false
coEvery { userService.create("new", any()) } returns User {
this.username = "new"
}
// existing user
coEvery { userService.exists("existing") } returns true
coEvery { userService.create("existing", any()) } returns null
coEvery { userService.delete(1) } returns true andThen false
// modified user
coEvery { userService.exists("modified") } returns true
coEvery { userService.exists(and(not("modified"), not("existing"))) } returns false
coEvery { userService.exists(1) } returns true
coEvery { userService.create("modified", any()) } returns null
}
private val kodein = DI {
import(mainModule, allowOverride = true)
bind<UserRepository>(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"] = "test123abc"
}
}
res.status() `should be equal to` HttpStatusCode.Created
res.content `should strictly be equal to json` """{username:"new"}"""
}
@Test
fun `create an existing user`() {
val res = testEngine.post("/user") {
json {
it["username"] = "existing"
it["password"] = "test123abc"
}
}
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"}"""
}
}
}
@@ -0,0 +1,223 @@
package integration.routing
import be.vandewalleh.Config
import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.entities.User
import be.vandewalleh.features.PasswordHash
import be.vandewalleh.mainModule
import be.vandewalleh.module
import be.vandewalleh.repositories.UserRepository
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.DI
import org.kodein.di.bind
import org.kodein.di.instance
import utils.*
import java.util.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AuthControllerKtTest {
private val userService = mockk<UserRepository>()
private val kodein = DI {
import(mainModule, allowOverride = true)
bind<UserRepository>(overrides = true) with instance(userService)
}
private val passwordHash by kodein.instance<PasswordHash>()
init {
val user = User {
password = passwordHash.crypt("password")
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 = passwordHash.crypt("right password")
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 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<SimpleJWT>(tag = "auth")
val token = jsonObject.getString("token")
authJwt.verifier.verify(token)
val refreshJwt by kodein.instance<SimpleJWT>(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<SimpleJWT>(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<SimpleJWT>(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<Config>()
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<SimpleJWT>(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<SimpleJWT>(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")
}
}
}
@@ -0,0 +1,257 @@
package integration.services
import am.ik.yavi.builder.ValidatorBuilder
import am.ik.yavi.core.CustomConstraint
import am.ik.yavi.core.Validator
import be.vandewalleh.entities.Note
import be.vandewalleh.features.Migration
import be.vandewalleh.mainModule
import be.vandewalleh.repositories.NoteRepository
import be.vandewalleh.repositories.UserRepository
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.util.StdDateFormat
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.github.javafaker.Faker
import kotlinx.coroutines.runBlocking
import me.liuwj.ktorm.jackson.*
import org.amshove.kluent.*
import org.junit.jupiter.api.*
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.instance
import org.kodein.di.singleton
import utils.KMariadbContainer
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class NoteRepositoryTest {
@Nested
inner class DB {
private val mariadb = KMariadbContainer().apply { start() }
private val kodein = DI {
import(mainModule, allowOverride = true)
bind(overrides = true) from singleton { mariadb.datasource() }
}
init {
val migration by kodein.instance<Migration>()
migration.migrate()
}
@Test
fun run() {
val userService by kodein.instance<UserRepository>()
val user = runBlocking { userService.create("test", "test")!! }
val noteService by kodein.instance<NoteRepository>()
val note = runBlocking {
noteService.create(
user.id,
Note {
this.title = "a note"
this.markdown =
"""
|# Title
|
|😝😝😝😝
|another line
""".trimMargin()
this.tags = listOf("a", "tag")
}
)
}
println(note)
val objectMapper = ObjectMapper().apply {
registerModule(JavaTimeModule())
registerModule(KtormModule())
disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
dateFormat = StdDateFormat()
}
val json = objectMapper.writeValueAsString(note)
println(json)
}
@Test
fun `test tag list`() {
val userService by kodein.instance<UserRepository>()
val user = runBlocking { userService.create("test", "test")!! }
val user2 = runBlocking { userService.create("user2", "test")!! }
val noteService by kodein.instance<NoteRepository>()
runBlocking {
noteService.create(
user.id,
Note {
title = "test"
markdown = ""
tags = listOf("same")
}
)
noteService.create(
user.id,
Note {
title = "test2"
markdown = ""
tags = listOf("same")
}
)
noteService.create(
user.id,
Note {
title = "test3"
markdown = ""
tags = listOf("another")
}
)
noteService.create(
user2.id,
Note {
title = "test"
markdown = ""
tags = listOf("user2")
}
)
}
val user1Tags = runBlocking { noteService.getTags(user.id) }
user1Tags `should be equal to` listOf("same", "another")
val user2Tags = runBlocking { noteService.getTags(user2.id) }
user2Tags `should be equal to` listOf("user2")
}
@Test
fun `test patch note`() {
val noteService by kodein.instance<NoteRepository>()
val userService by kodein.instance<UserRepository>()
val user = runBlocking { userService.create(Faker().name().username(), "test") }!!
val note = runBlocking {
noteService.create(
user.id,
Note {
this.title = "title"
this.markdown = "old content"
this.tags = emptyList()
}
)
}
val get = runBlocking { noteService.find(user.id, note.uuid) }
runBlocking {
noteService.updateNote(
user.id,
Note {
uuid = note.uuid
title = "new title"
}
)
}
val updated = runBlocking { noteService.find(user.id, note.uuid) }
println("updated: $updated")
}
}
@Nested
inner class NoteValidation {
@Test
fun `test update constraints`() {
val fieldPresentConstraint = object : CustomConstraint<Note> {
override fun defaultMessageFormat() = "fmt {0} {1} {2}"
override fun messageKey() = "title|content|tags"
override fun test(note: Note): Boolean {
val hasTitle = note["title"] != null
val hasContent = note["content"] != null
val hasTags = note["tags"] != null && note.tags.isNotEmpty()
return hasTitle || hasContent || hasTags
}
}
val userValidator: Validator<Note> = ValidatorBuilder<Note>()
.constraintOnTarget(fieldPresentConstraint, "present")
.build()
userValidator.validate(
Note {
title = "this is a title"
}
).isValid `should be equal to` true
userValidator.validate(
Note {
markdown = "this is a title"
}
).isValid `should be equal to` true
userValidator.validate(
Note {
tags = emptyList()
}
).isValid `should be equal to` false
userValidator.validate(
Note {
tags = listOf("tags")
}
).isValid `should be equal to` true
userValidator.validate(
Note {
tags = listOf("tags")
title = "This is a title"
}
).isValid `should be equal to` true
userValidator.validate(
Note {
tags = listOf("tags")
title = "This is a title"
markdown =
"""
|# This is
|
|some markdown content
""".trimMargin()
}
).isValid `should be equal to` true
userValidator.validate(
Note {
tags = listOf("tags")
title = "This is a title"
markdown =
"""
|# This is
|
|some markdown content
""".trimMargin()
}
).isValid `should be equal to` true
userValidator.validate(Note()).isValid `should be equal to` false
}
}
@Nested
inner class NoteEntity {
@Test
fun `test entity`() {
val objectMapper = ObjectMapper().apply {
registerModule(JavaTimeModule())
registerModule(KtormModule())
disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
dateFormat = StdDateFormat()
}
val note: Note = objectMapper.readValue("""{"uuid": "2007e4d7-2986-4188-bde1-b99916d94bad"}""")
println(note.uuid)
println(note.uuid::class.qualifiedName)
println(note.uuid.leastSignificantBits)
println(note.uuid.mostSignificantBits)
}
}
}
@@ -0,0 +1,66 @@
package integration.services
import be.vandewalleh.features.Migration
import be.vandewalleh.mainModule
import be.vandewalleh.repositories.UserRepository
import kotlinx.coroutines.runBlocking
import org.amshove.kluent.*
import org.junit.jupiter.api.*
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.instance
import org.kodein.di.singleton
import utils.KMariadbContainer
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
class UserRepositoryTest {
private val mariadb = KMariadbContainer().apply { start() }
private val kodein = DI {
import(mainModule, allowOverride = true)
bind(overrides = true) from singleton { mariadb.datasource() }
}
private val userService by kodein.instance<UserRepository>()
init {
val migration by kodein.instance<Migration>()
migration.migrate()
}
@Test
@Order(1)
fun `test create user`() {
runBlocking {
val username = "hubert"
val password = "password"
userService.create(username, password)
val user = userService.find(username)
user `should not be` null
user?.username `should be equal to` username
}
}
@Test
@Order(2)
fun `test create same user`() {
runBlocking {
userService.create(username = "hubert", password = "password") `should be` null
}
}
@Test
@Order(3)
fun `test delete user`() {
runBlocking {
val id = userService.find("hubert")!!.id
userService.delete(id)
userService.find("hubert") `should be` null
userService.find(id) `should be` null
}
}
}
+66
View File
@@ -0,0 +1,66 @@
package unit
import be.vandewalleh.markdown.Markdown
import be.vandewalleh.markdown.Meta
import org.amshove.kluent.*
import org.junit.jupiter.api.*
class MarkdownTest {
@Test
fun a() {
val md = Markdown()
fun Markdown.convertTrim(input: String) = convertToMarkdown(input).trim()
md.convertTrim("# title") `should be equal to` "<h1>title</h1>"
md.convertTrim(
"""
|- 1
|- 2
|- 3
""".trimMargin()
) `should be equal to`
"""
|<ul>
|<li>1</li>
|<li>2</li>
|<li>3</li>
|</ul>
""".trimMargin()
// md.parseMeta("title: test") `should be equal to` Meta("test")
md.parseMeta(
"""
|title: test
|tags:
| - a
| - b
|""".trimMargin()
) `should be equal to`
Meta("test", listOf("a", "b"))
}
@Test
fun testMeta() {
val md = Markdown()
val out = md.renderDocument(
"""
|
|---
|
|title: test
|tags: [a,b]
|---
|
|# Title
|
|- a
|- b
""".trimMargin()
)
println(out)
}
}
@@ -0,0 +1,36 @@
package unit.validation
import be.vandewalleh.entities.User
import be.vandewalleh.validation.registerValidator
import org.amshove.kluent.*
import org.junit.jupiter.api.*
import utils.firstInvalid
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class RegisterValidationTest {
@Test
fun `valid register test`() {
val violations = registerValidator.validate(
User {
username = "hubert"
password = "definitelyNotMyPassword"
}
)
violations.isValid `should be equal to` true
}
@Test
fun `username too long test`() {
val violations = registerValidator.validate(
User {
username = "6X9iboWmEOWjVjkO328ReTJ1gGPTTmB/ZGgBLhB6EzAJoWkJht8"
password = "definitelyNotMyPassword"
}
)
violations.isValid `should be equal to` false
violations.firstInvalid `should be equal to` "username"
}
}
+19
View 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
View 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)
}
+17
View File
@@ -0,0 +1,17 @@
package utils
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.testcontainers.containers.MariaDBContainer
class KMariadbContainer : MariaDBContainer<KMariadbContainer>() {
fun datasource(): HikariDataSource {
val hikariConfig = HikariConfig().apply {
jdbcUrl = this@KMariadbContainer.jdbcUrl
username = this@KMariadbContainer.username
password = this@KMariadbContainer.password
}
return HikariDataSource(hikariConfig)
}
}
+51
View File
@@ -0,0 +1,51 @@
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
+5
View File
@@ -0,0 +1,5 @@
package utils
import org.json.JSONObject
fun JSONObject.keyList(): List<Any?> = keys().asSequence().toList()
+6
View File
@@ -0,0 +1,6 @@
package utils
import am.ik.yavi.core.ConstraintViolations
val ConstraintViolations.firstInvalid: Any?
get() = this.violations().firstOrNull()?.name()