Upgrade to ktorm 3.0.0, remove email field from users

This commit is contained in:
Hubert Van De Walle 2020-06-18 16:33:18 +02:00
parent 3221b490ea
commit 214286a6eb
17 changed files with 90 additions and 225 deletions

View File

@ -12,7 +12,7 @@
<kotlin_version>1.3.70</kotlin_version> <kotlin_version>1.3.70</kotlin_version>
<logback_version>1.2.1</logback_version> <logback_version>1.2.1</logback_version>
<junit_version>5.6.2</junit_version> <junit_version>5.6.2</junit_version>
<ktorm_version>2.7.2</ktorm_version> <ktorm_version>3.0.0</ktorm_version>
<mariadb_version>2.6.0</mariadb_version> <mariadb_version>2.6.0</mariadb_version>
<kodein_version>6.5.4</kodein_version> <kodein_version>6.5.4</kodein_version>
<flyway_version>6.3.3</flyway_version> <flyway_version>6.3.3</flyway_version>

View File

@ -2,12 +2,8 @@ create table Users
( (
id int auto_increment primary key, id int auto_increment primary key,
username varchar(50) not null, username varchar(50) not null,
email varchar(255) not null,
password varchar(255) not null, password varchar(255) not null,
created_at datetime not null,
last_login datetime null,
constraint email unique (email),
constraint username unique (username) constraint username unique (username)
); );

View File

@ -1,15 +1,14 @@
package be.vandewalleh.entities package be.vandewalleh.entities
import com.fasterxml.jackson.annotation.JsonProperty
import me.liuwj.ktorm.entity.* import me.liuwj.ktorm.entity.*
import java.time.LocalDateTime
interface User : Entity<User> { interface User : Entity<User> {
companion object : Entity.Factory<User>() companion object : Entity.Factory<User>()
val id: Int val id: Int
var username: String var username: String
var email: String
@get:JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
var password: String var password: String
var createdAt: LocalDateTime
var lastLogin: LocalDateTime?
} }

View File

@ -5,7 +5,6 @@ import java.nio.ByteBuffer
import java.sql.PreparedStatement import java.sql.PreparedStatement
import java.sql.ResultSet import java.sql.ResultSet
import java.sql.Types import java.sql.Types
import java.util.*
import java.util.UUID as JavaUUID import java.util.UUID as JavaUUID
class UuidBinarySqlType : SqlType<JavaUUID>(Types.BINARY, typeName = "uuidBinary") { class UuidBinarySqlType : SqlType<JavaUUID>(Types.BINARY, typeName = "uuidBinary") {
@ -23,6 +22,6 @@ class UuidBinarySqlType : SqlType<JavaUUID>(Types.BINARY, typeName = "uuidBinary
} }
} }
fun <E : Any> BaseTable<E>.uuidBinary(name: String): BaseTable<E>.ColumnRegistration<JavaUUID> { fun <E : Any> BaseTable<E>.uuidBinary(name: String): Column<JavaUUID> {
return registerColumn(name, UuidBinarySqlType()) return registerColumn(name, UuidBinarySqlType())
} }

View File

@ -27,7 +27,7 @@ fun Routing.auth(kodein: Kodein) {
post("/user/login") { post("/user/login") {
val credential = call.receive<UsernamePasswordCredential>() val credential = call.receive<UsernamePasswordCredential>()
val user = userService.getFromUsername(credential.username) val user = userService.find(credential.username)
?: return@post call.respondStatus(HttpStatusCode.Unauthorized) ?: return@post call.respondStatus(HttpStatusCode.Unauthorized)
if (!BCrypt.checkpw(credential.password, user.password)) { if (!BCrypt.checkpw(credential.password, user.password)) {
@ -51,7 +51,7 @@ fun Routing.auth(kodein: Kodein) {
return@post call.respondStatus(HttpStatusCode.Unauthorized) return@post call.respondStatus(HttpStatusCode.Unauthorized)
} }
if (!userService.userExists(id)) if (!userService.exists(id))
return@post call.respondStatus(HttpStatusCode.Unauthorized) return@post call.respondStatus(HttpStatusCode.Unauthorized)
val response = DualToken( val response = DualToken(
@ -63,9 +63,8 @@ fun Routing.auth(kodein: Kodein) {
authenticate { authenticate {
get("/user/me") { get("/user/me") {
// retrieve email from token
val id = call.principal<UserDbIdPrincipal>()!!.id val id = call.principal<UserDbIdPrincipal>()!!.id
val info = userService.getUserInfo(id) val info = userService.find(id)
if (info != null) call.respond(mapOf("user" to info)) if (info != null) call.respond(mapOf("user" to info))
else call.respondStatus(HttpStatusCode.Unauthorized) else call.respondStatus(HttpStatusCode.Unauthorized)
} }

View File

@ -1,6 +1,5 @@
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
@ -9,13 +8,11 @@ import be.vandewalleh.validation.user.registerValidator
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.response.* import io.ktor.response.*
import io.ktor.routing.* 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>()
@ -24,32 +21,20 @@ fun Routing.user(kodein: Kodein) {
post { post {
val user = call.receiveValidated(registerValidator) val user = call.receiveValidated(registerValidator)
if (userService.userExists(user.username, user.email)) if (userService.exists(user.username))
return@post call.respondStatus(HttpStatusCode.Conflict) return@post call.respondStatus(HttpStatusCode.Conflict)
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt()) val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
userService.createUser(user.username, user.email, hashedPassword) val newUser = userService.create(user.username, hashedPassword)
?: return@post call.respondStatus(HttpStatusCode.Conflict)
call.respondStatus(HttpStatusCode.Created) call.respond(HttpStatusCode.Created, newUser)
} }
authenticate { authenticate {
put {
val user = call.receiveValidated(registerValidator)
if (userService.userExists(user.username, user.email))
return@put call.respond(HttpStatusCode.Conflict)
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
userService.updateUser(call.userId(), user.username, user.email, hashedPassword)
call.respondStatus(HttpStatusCode.OK)
}
delete { delete {
val status = if (userService.deleteUser(call.userId())) val status = if (userService.delete(call.userId()))
HttpStatusCode.OK HttpStatusCode.OK
else else
HttpStatusCode.NotFound HttpStatusCode.NotFound

View File

@ -47,12 +47,11 @@ class NotesService(override val kodein: Kodein) : KodeinAware {
} }
fun noteExists(userId: Int, uuid: UUID): Boolean { fun noteExists(userId: Int, uuid: UUID): Boolean {
return db.from(Notes) return db.sequenceOf(Notes)
.select(Notes.uuid) .filterColumns { listOf(it.uuid) }
.where { Notes.userId eq userId } .find {
.where { Notes.uuid eq uuid } it.userId eq userId and (it.uuid eq uuid)
.limit(0, 1) } == null
.toList().size == 1
} }
fun createNote(userId: Int, note: FullNoteCreateDTO): UUID { fun createNote(userId: Int, note: FullNoteCreateDTO): UUID {

View File

@ -20,62 +20,39 @@ import java.time.LocalDateTime
class UserService(override val kodein: Kodein) : KodeinAware { class UserService(override val kodein: Kodein) : KodeinAware {
private val db by instance<Database>() private val db by instance<Database>()
/**
* returns a user ID if present or null
*/
suspend fun getUserId(userEmail: String): Int? = launchIo {
db.from(Users)
.select(Users.id)
.where { Users.email eq userEmail }
.map { it[Users.id] }
.firstOrNull()
}
/** /**
* returns a user email and password from it's username if found or null * returns a user from it's username if found or null
*/ */
suspend fun getFromUsername(username: String): User? = launchIo { suspend fun find(username: String): User? = launchIo {
db.from(Users) db.sequenceOf(Users, withReferences = false)
.select(Users.email, Users.password, Users.id) .find { it.username eq username }
.where { Users.username eq username }
.map { row ->
Users.createEntity(row)
}
.firstOrNull()
} }
suspend fun userExists(username: String, email: String): Boolean = launchIo { suspend fun find(id: Int): User? = launchIo {
db.from(Users) db.sequenceOf(Users, withReferences = false)
.select(Users.id) .find { it.id eq id }
.where { (Users.username eq username) or (Users.email eq email) }
.firstOrNull() != null
} }
suspend fun userExists(userId: Int): Boolean = launchIo {
db.from(Users) suspend fun exists(username: String) = launchIo {
.select(Users.id) db.sequenceOf(Users, withReferences = false)
.where { Users.id eq userId } .any { it.username eq username }
.firstOrNull() != null
} }
suspend fun getUserInfo(id: Int): User? = launchIo { suspend fun exists(userId: Int) = launchIo {
db.from(Users) db.sequenceOf(Users).any { it.id eq userId }
.select(Users.email, Users.username)
.where { Users.id eq id }
.map { Users.createEntity(it) }
.firstOrNull()
} }
/** /**
* create a new user * create a new user
* password should already be hashed * password should already be hashed
*/ */
suspend fun createUser(username: String, email: String, hashedPassword: String): User? { suspend fun create(username: String, hashedPassword: String): User? {
val newUser = User { val newUser = User {
this.username = username this.username = username
this.email = email
password = hashedPassword password = hashedPassword
createdAt = LocalDateTime.now()
} }
return try { return try {
@ -90,25 +67,11 @@ class UserService(override val kodein: Kodein) : KodeinAware {
} }
} }
suspend fun updateUser(userId: Int, username: String, email: String, hashedPassword: String): Unit = launchIo { suspend fun delete(userId: Int): Boolean = launchIo {
db.useTransaction { val updateCount = db.useTransaction {
db.update(Users) {
it.username to username
it.email to email
it.password to hashedPassword
where {
it.id eq userId
}
}
}
}
suspend fun deleteUser(userId: Int): Boolean = launchIo {
db.useTransaction {
db.delete(Users) { it.id eq userId } db.delete(Users) { it.id eq userId }
} }
}.let { when (updateCount) {
when (it) {
1 -> true 1 -> true
0 -> false 0 -> false
else -> error("??") else -> error("??")

View File

@ -5,10 +5,10 @@ import be.vandewalleh.extensions.uuidBinary
import me.liuwj.ktorm.schema.* import me.liuwj.ktorm.schema.*
object Chapters : Table<Chapter>("Chapters") { object Chapters : Table<Chapter>("Chapters") {
val id by int("id").primaryKey().bindTo { it.id } val id = int("id").primaryKey().bindTo { it.id }
val number by int("number").bindTo { it.number } val number = int("number").bindTo { it.number }
val content by text("content").bindTo { it.content } val content = text("content").bindTo { it.content }
val title by varchar("title").bindTo { it.title } val title = varchar("title").bindTo { it.title }
val noteUuid by uuidBinary("note_uuid").references(Notes) { it.note } val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
val note get() = noteUuid.referenceTable as Notes val note get() = noteUuid.referenceTable as Notes
} }

View File

@ -5,9 +5,9 @@ import be.vandewalleh.extensions.uuidBinary
import me.liuwj.ktorm.schema.* import me.liuwj.ktorm.schema.*
object Notes : Table<Note>("Notes") { object Notes : Table<Note>("Notes") {
val uuid by uuidBinary("uuid").primaryKey().bindTo { it.uuid } val uuid = uuidBinary("uuid").primaryKey().bindTo { it.uuid }
val title by varchar("title").bindTo { it.title } val title = varchar("title").bindTo { it.title }
val userId by int("user_id").references(Users) { it.user } val userId = int("user_id").references(Users) { it.user }
val updatedAt by datetime("updated_at").bindTo { it.updatedAt } val updatedAt = datetime("updated_at").bindTo { it.updatedAt }
val user get() = userId.referenceTable as Users val user get() = userId.referenceTable as Users
} }

View File

@ -5,8 +5,8 @@ import be.vandewalleh.extensions.uuidBinary
import me.liuwj.ktorm.schema.* import me.liuwj.ktorm.schema.*
object Tags : Table<Tag>("Tags") { object Tags : Table<Tag>("Tags") {
val id by int("id").primaryKey().bindTo { it.id } val id = int("id").primaryKey().bindTo { it.id }
val name by varchar("name").bindTo { it.name } val name = varchar("name").bindTo { it.name }
val noteUuid by uuidBinary("note_uuid").references(Notes) { it.note } val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
val note get() = noteUuid.referenceTable as Notes val note get() = noteUuid.referenceTable as Notes
} }

View File

@ -4,10 +4,7 @@ import be.vandewalleh.entities.User
import me.liuwj.ktorm.schema.* import me.liuwj.ktorm.schema.*
object Users : Table<User>("Users") { object Users : Table<User>("Users") {
val id by int("id").primaryKey().bindTo { it.id } val id = int("id").primaryKey().bindTo { it.id }
val username by varchar("username").bindTo { it.username } val username = varchar("username").bindTo { it.username }
val email by varchar("email").bindTo { it.email } val password = varchar("password").bindTo { it.password }
val password by varchar("password").bindTo { it.password }
val createdAt by datetime("created_at").bindTo { it.createdAt }
val lastLogin by datetime("last_login").bindTo { it.lastLogin }
} }

View File

@ -9,9 +9,6 @@ val registerValidator: Validator<User> = ValidatorBuilder.of<User>()
.konstraint(User::username) { .konstraint(User::username) {
notNull().lessThanOrEqual(50).greaterThanOrEqual(3) notNull().lessThanOrEqual(50).greaterThanOrEqual(3)
} }
.konstraint(User::email) {
notNull().notEmpty().lessThanOrEqual(255).email()
}
.konstraint(User::password) { .konstraint(User::password) {
notNull().greaterThanOrEqual(6) notNull().greaterThanOrEqual(6)
} }

View File

@ -13,7 +13,6 @@ import io.ktor.server.testing.*
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.coVerify import io.mockk.coVerify
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify
import org.amshove.kluent.* import org.amshove.kluent.*
import org.json.JSONObject import org.json.JSONObject
import org.junit.jupiter.api.* import org.junit.jupiter.api.*
@ -37,11 +36,10 @@ class AuthControllerKtTest {
} }
user["id"] = 1 user["id"] = 1
coEvery { userService.getFromUsername("existing") } returns user coEvery { userService.find("existing") } returns user
coEvery { userService.userExists(1) } returns true coEvery { userService.exists(1) } returns true
coEvery { userService.getUserInfo(1) } returns User { coEvery { userService.find(1) } returns User {
username = "existing" username = "existing"
email = "existing@mail.com"
} }
val user2 = User { val user2 = User {
@ -49,12 +47,12 @@ class AuthControllerKtTest {
username = "wrong" username = "wrong"
} }
user["id"] = 2 user["id"] = 2
coEvery { userService.getFromUsername("wrong") } returns user2 coEvery { userService.find("wrong") } returns user2
coEvery { userService.getFromUsername("notExisting") } returns null coEvery { userService.find("notExisting") } returns null
coEvery { userService.userExists(3) } returns false coEvery { userService.exists(3) } returns false
coEvery { userService.getUserInfo(3) } returns null coEvery { userService.find(3) } returns null
} }
@ -79,7 +77,7 @@ class AuthControllerKtTest {
} }
} }
coVerify { userService.getFromUsername("existing") } coVerify { userService.find("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)
@ -107,7 +105,7 @@ class AuthControllerKtTest {
} }
} }
coVerify { userService.getFromUsername("wrong") } coVerify { userService.find("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"}"""
@ -122,7 +120,7 @@ class AuthControllerKtTest {
} }
} }
coVerify { userService.getFromUsername("notExisting") } coVerify { userService.find("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"}"""
@ -155,7 +153,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")
coVerify { userService.userExists(1) } coVerify { userService.exists(1) }
res.status() `should be equal to` HttpStatusCode.OK res.status() `should be equal to` HttpStatusCode.OK
} }
@ -170,7 +168,7 @@ class AuthControllerKtTest {
} }
} }
coVerify { userService.userExists(3) } coVerify { userService.exists(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"}"""
} }
@ -207,7 +205,7 @@ class AuthControllerKtTest {
val res = testEngine.get("/user/me") { val res = testEngine.get("/user/me") {
setToken(token) setToken(token)
} }
res.content `should strictly be equal to json` """{user:{username:"existing", email: "existing@mail.com"}}""" res.content `should strictly be equal to json` """{user:{username:"existing"}}"""
res.status() `should be equal to` HttpStatusCode.OK res.status() `should be equal to` HttpStatusCode.OK
} }

View File

@ -15,7 +15,6 @@ import org.kodein.di.Kodein
import org.kodein.di.generic.bind import org.kodein.di.generic.bind
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
import utils.* import utils.*
import java.time.LocalDateTime
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserControllerKtTest { class UserControllerKtTest {
@ -24,31 +23,21 @@ class UserControllerKtTest {
init { init {
// new user // new user
coEvery { userService.userExists("new", "new@test.com") } returns false coEvery { userService.exists("new") } returns false
coEvery { userService.createUser("new", "new@test.com", any()) } returns User { coEvery { userService.create("new", any()) } returns User {
this.createdAt = LocalDateTime.now()
this.username = "new" this.username = "new"
this.email = "new@test.com"
} }
// existing user // existing user
coEvery { userService.userExists("existing", "existing@test.com") } returns true coEvery { userService.exists("existing") } returns true
coEvery { userService.createUser("existing", "existing@test.com", any()) } returns null coEvery { userService.create("existing", any()) } returns null
coEvery { userService.getUserId("existing@test.com") } returns 1 coEvery { userService.delete(1) } returns true andThen false
coEvery { userService.deleteUser(1) } returns true andThen false
// modified user // modified user
coEvery { userService.userExists("modified", "modified@test.com") } returns true coEvery { userService.exists("modified") } returns true
coEvery { coEvery { userService.exists(and(not("modified"), not("existing"))) } returns false
userService.userExists( coEvery { userService.exists(1) } returns true
and(not("modified"), not("existing")), coEvery { userService.create("modified", any()) } returns null
and(not("modified@test.com"), not("existing@test.com"))
)
} returns false
coEvery { userService.userExists(1) } returns true
coEvery { userService.createUser("modified", "modified@test.com", any()) } returns null
coEvery { userService.getUserId("modified@test.com") } returns 1
coEvery { userService.updateUser(1, "ThisIsMyNewName", "ThisIsMyNewName@mail.com", any()) } returns Unit
} }
@ -71,11 +60,10 @@ class UserControllerKtTest {
json { json {
it["username"] = "new" it["username"] = "new"
it["password"] = "test123abc" it["password"] = "test123abc"
it["email"] = "new@test.com"
} }
} }
res.status() `should be equal to` HttpStatusCode.Created res.status() `should be equal to` HttpStatusCode.Created
res.content `should be equal to json` """{msg:"Created"}""" res.content `should strictly be equal to json` """{username:"new"}"""
} }
@Test @Test
@ -83,7 +71,6 @@ class UserControllerKtTest {
val res = testEngine.post("/user") { val res = testEngine.post("/user") {
json { json {
it["username"] = "existing" it["username"] = "existing"
it["email"] = "existing@test.com"
it["password"] = "test123abc" it["password"] = "test123abc"
} }
} }
@ -116,28 +103,4 @@ class UserControllerKtTest {
} }
} }
@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"
it["password"] = "ThisIsMyCurrentPassword"
}
}
res.status() `should be equal to` HttpStatusCode.OK
res.content `should be equal to json` """{msg:"OK"}"""
}
}
} }

View File

@ -39,17 +39,12 @@ class UserServiceTest {
fun `test create user`() { fun `test create user`() {
runBlocking { runBlocking {
val username = "hubert" val username = "hubert"
val email = "a@a"
val password = "password" val password = "password"
userService.createUser(username, email, password) userService.create(username, password)
val id = userService.getUserId(email) val user = userService.find(username)
id `should not be` null user `should not be` null
user?.username `should be equal to` username
userService.getUserInfo(id!!)!!.let {
it.username `should be equal to` username
it.email `should be equal to` email
}
} }
} }
@ -57,7 +52,7 @@ class UserServiceTest {
@Order(2) @Order(2)
fun `test create same user`() { fun `test create same user`() {
runBlocking { runBlocking {
userService.createUser(username = "hubert", hashedPassword = "password", email = "a@a") `should be` null userService.create(username = "hubert", hashedPassword = "password") `should be` null
} }
} }
@ -65,12 +60,11 @@ class UserServiceTest {
@Order(3) @Order(3)
fun `test delete user`() { fun `test delete user`() {
runBlocking { runBlocking {
val email = "a@a" val id = userService.find("hubert")!!.id
val id = userService.getUserId(email)!! userService.delete(id)
userService.deleteUser(id)
userService.getUserId(email) `should be` null userService.find("hubert") `should be` null
userService.getUserInfo(id) `should be` null userService.find(id) `should be` null
} }
} }

View File

@ -14,41 +14,17 @@ class RegisterValidationTest {
val violations = registerValidator.validate(User { val violations = registerValidator.validate(User {
username = "hubert" username = "hubert"
password = "definitelyNotMyPassword" password = "definitelyNotMyPassword"
email = "test@mail.com"
}) })
violations.isValid `should be equal to` true 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 @Test
fun `username too long test`() { fun `username too long test`() {
val violations = registerValidator.validate(User { val violations = registerValidator.validate(User {
username = "6X9iboWmEOWjVjkO328ReTJ1gGPTTmB/ZGgBLhB6EzAJoWkJht8" username = "6X9iboWmEOWjVjkO328ReTJ1gGPTTmB/ZGgBLhB6EzAJoWkJht8"
password = "definitelyNotMyPassword" password = "definitelyNotMyPassword"
email = "test@mail.com"
}) })
violations.isValid `should be equal to` false violations.isValid `should be equal to` false