diff --git a/api/pom.xml b/api/pom.xml
index 40e5d0b..3df2ab1 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -160,6 +160,11 @@
hoplite-yaml
${hoplite_version}
+
+ am.ik.yavi
+ yavi
+ 0.4.0
+
com.github.javafaker
javafaker
diff --git a/api/src/features/ErrorFeature.kt b/api/src/features/ErrorFeature.kt
index c901e7e..ee59c87 100644
--- a/api/src/features/ErrorFeature.kt
+++ b/api/src/features/ErrorFeature.kt
@@ -1,5 +1,7 @@
package be.vandewalleh.features
+import am.ik.yavi.core.ViolationDetail
+import be.vandewalleh.validation.ValidationException
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
@@ -11,5 +13,13 @@ fun Application.handleErrors() {
exception {
call.respond(HttpStatusCode.BadRequest)
}
+ exception {
+ val error = ViolationError(it.details[0])
+ call.respond(HttpStatusCode.BadRequest, error)
+ }
}
-}
\ No newline at end of file
+}
+
+class ViolationError(detail: ViolationDetail) {
+ val msg = detail.defaultMessage
+}
diff --git a/api/src/routing/UserController.kt b/api/src/routing/UserController.kt
index bbdbea9..4205f85 100644
--- a/api/src/routing/UserController.kt
+++ b/api/src/routing/UserController.kt
@@ -4,6 +4,8 @@ import be.vandewalleh.entities.User
import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.extensions.userId
import be.vandewalleh.services.UserService
+import be.vandewalleh.validation.receiveValidated
+import be.vandewalleh.validation.user.registerValidator
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
@@ -18,14 +20,9 @@ import java.time.LocalDateTime
fun Routing.user(kodein: Kodein) {
val userService by kodein.instance()
- post("/user/test") {
- val user = call.receive()
- call.respond(user)
- }
-
route("/user") {
post {
- val user = call.receive()
+ val user = call.receiveValidated(registerValidator)
if (userService.userExists(user.username, user.email))
return@post call.respondStatus(HttpStatusCode.Conflict)
@@ -39,7 +36,7 @@ fun Routing.user(kodein: Kodein) {
authenticate {
put {
- val user = call.receive()
+ val user = call.receiveValidated(registerValidator)
if (userService.userExists(user.username, user.email))
return@put call.respond(HttpStatusCode.Conflict)
diff --git a/api/src/validation/ValidationExtensions.kt b/api/src/validation/ValidationExtensions.kt
new file mode 100644
index 0000000..1d00401
--- /dev/null
+++ b/api/src/validation/ValidationExtensions.kt
@@ -0,0 +1,14 @@
+package be.vandewalleh.validation
+
+import am.ik.yavi.core.Validator
+import am.ik.yavi.core.ViolationDetail
+import io.ktor.application.*
+import io.ktor.request.*
+
+suspend inline fun ApplicationCall.receiveValidated(validator: Validator): T {
+ val value: T = receive()
+ validator.validate(value).throwIfInvalid { ValidationException(it.details()) }
+ return value
+}
+
+data class ValidationException(val details: List) : RuntimeException()
diff --git a/api/src/validation/user/UserValidation.kt b/api/src/validation/user/UserValidation.kt
new file mode 100644
index 0000000..6f2063e
--- /dev/null
+++ b/api/src/validation/user/UserValidation.kt
@@ -0,0 +1,18 @@
+package be.vandewalleh.validation.user
+
+import am.ik.yavi.builder.ValidatorBuilder
+import am.ik.yavi.builder.konstraint
+import am.ik.yavi.core.Validator
+import be.vandewalleh.entities.User
+
+val registerValidator: Validator = ValidatorBuilder.of()
+ .konstraint(User::username) {
+ notNull().lessThanOrEqual(50).greaterThanOrEqual(3)
+ }
+ .konstraint(User::email) {
+ notNull().notEmpty().lessThanOrEqual(255).email()
+ }
+ .konstraint(User::password) {
+ notNull().greaterThanOrEqual(6)
+ }
+ .build()
diff --git a/api/test/routing/UserControllerKtTest.kt b/api/test/integration/routing/UserControllerKtTest.kt
similarity index 95%
rename from api/test/routing/UserControllerKtTest.kt
rename to api/test/integration/routing/UserControllerKtTest.kt
index 8e521fc..04fe9d1 100644
--- a/api/test/routing/UserControllerKtTest.kt
+++ b/api/test/integration/routing/UserControllerKtTest.kt
@@ -1,4 +1,4 @@
-package routing
+package integration.routing
import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.entities.User
@@ -70,7 +70,7 @@ class UserControllerKtTest {
val res = testEngine.post("/user") {
json {
it["username"] = "new"
- it["password"] = "test"
+ it["password"] = "test123abc"
it["email"] = "new@test.com"
}
}
@@ -84,7 +84,7 @@ class UserControllerKtTest {
json {
it["username"] = "existing"
it["email"] = "existing@test.com"
- it["password"] = "test"
+ it["password"] = "test123abc"
}
}
res.status() `should be equal to` HttpStatusCode.Conflict
@@ -129,6 +129,7 @@ class UserControllerKtTest {
json {
it["username"] = "ThisIsMyNewName"
it["email"] = "ThisIsMyNewName@mail.com"
+ it["password"] = "ThisIsMyCurrentPassword"
}
}
diff --git a/api/test/services/UserServiceTest.kt b/api/test/integration/services/UserServiceTest.kt
similarity index 98%
rename from api/test/services/UserServiceTest.kt
rename to api/test/integration/services/UserServiceTest.kt
index d9a6c57..4cb9b4c 100644
--- a/api/test/services/UserServiceTest.kt
+++ b/api/test/integration/services/UserServiceTest.kt
@@ -1,4 +1,4 @@
-package services
+package integration.services
import be.vandewalleh.mainModule
import be.vandewalleh.migrations.Migration
diff --git a/api/test/unit/validation/RegisterValidationTest.kt b/api/test/unit/validation/RegisterValidationTest.kt
new file mode 100644
index 0000000..b2ea374
--- /dev/null
+++ b/api/test/unit/validation/RegisterValidationTest.kt
@@ -0,0 +1,57 @@
+package unit.validation
+
+import be.vandewalleh.entities.User
+import be.vandewalleh.validation.user.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"
+ email = "test@mail.com"
+ })
+
+ violations.isValid `should be equal to` true
+ }
+
+ @Test
+ fun `invalid email test`() {
+ val violations = registerValidator.validate(User {
+ username = "hubert"
+ password = "definitelyNotMyPassword"
+ email = "teom"
+ })
+
+ violations.isValid `should be equal to` false
+ violations.firstInvalid `should be equal to` "email"
+ }
+
+ @Test
+ fun `missing email test`() {
+ val violations = registerValidator.validate(User {
+ username = "hubert"
+ password = "definitelyNotMyPassword"
+ })
+
+ violations.isValid `should be equal to` false
+ violations.firstInvalid `should be equal to` "email"
+ }
+
+ @Test
+ fun `username too long test`() {
+ val violations = registerValidator.validate(User {
+ username = "6X9iboWmEOWjVjkO328ReTJ1gGPTTmB/ZGgBLhB6EzAJoWkJht8"
+ password = "definitelyNotMyPassword"
+ email = "test@mail.com"
+ })
+
+ violations.isValid `should be equal to` false
+ violations.firstInvalid `should be equal to` "username"
+ }
+}
diff --git a/api/test/utils/ValidationExtensions.kt b/api/test/utils/ValidationExtensions.kt
new file mode 100644
index 0000000..91e461a
--- /dev/null
+++ b/api/test/utils/ValidationExtensions.kt
@@ -0,0 +1,6 @@
+package utils
+
+import am.ik.yavi.core.ConstraintViolations
+
+val ConstraintViolations.firstInvalid: Any?
+ get() = this.violations().firstOrNull()?.name()