Add user service tests

This commit is contained in:
Hubert Van De Walle 2020-06-14 19:18:33 +02:00
parent fc7fa6b5f7
commit 44859b1ecd
8 changed files with 142 additions and 133 deletions

View File

@ -161,6 +161,24 @@
<version>1.0.2</version> <version>1.0.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-tests</artifactId>
<version>${ktor_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mariadb</artifactId>
<version>1.14.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.amshove.kluent</groupId>
<artifactId>kluent</artifactId>
<version>1.61</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<sourceDirectory>${project.basedir}/src</sourceDirectory> <sourceDirectory>${project.basedir}/src</sourceDirectory>
@ -174,12 +192,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration> <version>3.0.0-M4</version>
<includes>
<include>Test*</include>
<include>*Test</include>
</includes>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
@ -210,7 +223,7 @@
<configuration> <configuration>
<experimentalCoroutines>enable</experimentalCoroutines> <experimentalCoroutines>enable</experimentalCoroutines>
<sourceDirs> <sourceDirs>
<sourceDir>${project.basedir}/src/test</sourceDir> <sourceDir>${project.basedir}/test</sourceDir>
</sourceDirs> </sourceDirs>
</configuration> </configuration>
</execution> </execution>

View File

@ -1,19 +1,19 @@
-- ON DELETE -> CASCADE -- ON DELETE -> CASCADE
ALTER TABLE `Notes` ALTER TABLE `Notes`
DROP CONSTRAINT `Notes_ibfk_1`; DROP CONSTRAINT IF EXISTS `Notes_ibfk_1`;
ALTER TABLE `Notes` ALTER TABLE `Notes`
ADD FOREIGN KEY (`user_id`) REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; ADD FOREIGN KEY (`user_id`) REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `Chapters` ALTER TABLE `Chapters`
DROP CONSTRAINT `Chapters_ibfk_1`; DROP CONSTRAINT IF EXISTS `Chapters_ibfk_1`;
ALTER TABLE `Chapters` ALTER TABLE `Chapters`
ADD FOREIGN KEY (`note_id`) REFERENCES `Notes` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; ADD FOREIGN KEY (`note_id`) REFERENCES `Notes` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `Tags` ALTER TABLE `Tags`
DROP CONSTRAINT `Tags_ibfk_1`; DROP CONSTRAINT IF EXISTS `Tags_ibfk_1`;
ALTER TABLE `Tags` ALTER TABLE `Tags`
ADD FOREIGN KEY (`note_id`) REFERENCES `Notes` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; ADD FOREIGN KEY (`note_id`) REFERENCES `Notes` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;

View File

@ -12,4 +12,6 @@
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>
<logger name="io.netty" level="INFO"/> <logger name="io.netty" level="INFO"/>
<logger name="org.flywaydb.core" level="INFO"/> <logger name="org.flywaydb.core" level="INFO"/>
<logger name="org.testcontainers" level="INFO"/>
<logger name="com.github.dockerjava" level="INFO"/>
</configuration> </configuration>

View File

@ -2,7 +2,6 @@ package be.vandewalleh.routing
import be.vandewalleh.extensions.respondStatus import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.extensions.userId import be.vandewalleh.extensions.userId
import be.vandewalleh.services.UserDto
import be.vandewalleh.services.UserService import be.vandewalleh.services.UserService
import io.ktor.application.* import io.ktor.application.*
import io.ktor.auth.* import io.ktor.auth.*
@ -26,9 +25,7 @@ fun Routing.user(kodein: Kodein) {
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt()) val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
userService.createUser( userService.createUser(user.username, user.email, hashedPassword)
UserDto(user.username, user.email, hashedPassword)
)
call.respondStatus(HttpStatusCode.Created) call.respondStatus(HttpStatusCode.Created)
} }
@ -42,10 +39,7 @@ fun Routing.user(kodein: Kodein) {
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt()) val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
userService.updateUser( userService.updateUser(call.userId(), user.username, user.email, hashedPassword)
call.userId(),
UserDto(user.username, user.email, hashedPassword)
)
call.respondStatus(HttpStatusCode.OK) call.respondStatus(HttpStatusCode.OK)
} }
@ -57,5 +51,6 @@ fun Routing.user(kodein: Kodein) {
} }
} }
}
} private data class UserDto(val username: String, val email: String, val password: String)

View File

@ -8,6 +8,7 @@ import me.liuwj.ktorm.entity.*
import org.kodein.di.Kodein import org.kodein.di.Kodein
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
import java.sql.SQLIntegrityConstraintViolationException
import java.time.LocalDateTime import java.time.LocalDateTime
/** /**
@ -28,19 +29,14 @@ class UserService(override val kodein: Kodein) : KodeinAware {
} }
/** /**
* returns a user email and password from it's email if found or null * returns a user email and password from it's username if found or null
*/ */
fun getFromUsername(username: String): UserSchema? { fun getFromUsername(username: String): User? {
return db.from(Users) return 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 ->
UserSchema( Users.createEntity(row)
row[Users.id]!!,
username,
row[Users.email]!!,
row[Users.password]!!
)
} }
.firstOrNull() .firstOrNull()
} }
@ -59,11 +55,11 @@ class UserService(override val kodein: Kodein) : KodeinAware {
.firstOrNull() != null .firstOrNull() != null
} }
fun getUserInfo(id: Int): UserInfoDto? { fun getUserInfo(id: Int): User? {
return db.from(Users) return db.from(Users)
.select(Users.email, Users.username) .select(Users.email, Users.username)
.where { Users.id eq id } .where { Users.id eq id }
.map { UserInfoDto(it[Users.username]!!, it[Users.email]!!) } .map { Users.createEntity(it) }
.firstOrNull() .firstOrNull()
} }
@ -71,25 +67,30 @@ 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(user: UserDto) { fun createUser(username: String, email: String, hashedPassword: String): User? {
db.useTransaction { try {
val newUser = User { db.useTransaction {
this.username = user.username val newUser = User {
this.email = user.email this.username = username
this.password = user.password this.email = email
this.createdAt = LocalDateTime.now() this.password = hashedPassword
} this.createdAt = LocalDateTime.now()
}
db.sequenceOf(Users).add(newUser) db.sequenceOf(Users).add(newUser)
return newUser
}
} catch (e: SQLIntegrityConstraintViolationException) {
return null
} }
} }
fun updateUser(userId: Int, user: UserDto) { fun updateUser(userId: Int, username: String, email: String, hashedPassword: String) {
db.useTransaction { db.useTransaction {
db.update(Users) { db.update(Users) {
it.username to user.username it.username to username
it.email to user.email it.email to email
it.password to user.password it.password to hashedPassword
where { where {
it.id eq userId it.id eq userId
} }
@ -104,6 +105,3 @@ class UserService(override val kodein: Kodein) : KodeinAware {
} }
} }
data class UserSchema(val id: Int, val username: String, val email: String, val password: String)
data class UserDto(val username: String, val email: String, val password: String)
data class UserInfoDto(val username: String, val email: String)

View File

@ -1,37 +0,0 @@
import be.vandewalleh.services.ChapterDTO
import be.vandewalleh.services.FullNoteCreateDTO
import be.vandewalleh.services.NotesService
import com.github.javafaker.Faker
import org.junit.jupiter.api.Test
import org.kodein.di.generic.instance
class FakeDataTest {
val notesService by kodein.instance<NotesService>()
@Test
fun addNotes() {
val faker = Faker()
val title = faker.hobbit().quote()
val tags = listOf(
faker.beer().name(),
faker.beer().yeast()
)
val chapters = listOf(
ChapterDTO(
faker.animal().name(),
faker.lorem().paragraph()
),
ChapterDTO(
faker.animal().name(),
faker.lorem().paragraph()
)
)
val note = FullNoteCreateDTO(title, tags, chapters)
notesService.createNote(1, note)
}
}

View File

@ -1,50 +0,0 @@
import be.vandewalleh.services.NotesService
import be.vandewalleh.services.UserService
import com.hekeki.huckleberry.Benchmark
import com.hekeki.huckleberry.BenchmarkRunner
import com.hekeki.huckleberry.BenchmarkTest
import com.hekeki.huckleberry.TimeUnit
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import me.liuwj.ktorm.database.*
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.singleton
val hikariConfig = HikariConfig().apply {
jdbcUrl = "jdbc:mariadb://localhost:3306/notes"
username = "notes"
password = "notes"
}
val dataSource = HikariDataSource(hikariConfig)
val db = Database.Companion.connect(dataSource)
val kodein = Kodein {
bind<Database>() with singleton { db }
bind<UserService>() with singleton { UserService(this.kodein) }
bind<NotesService>() with singleton { NotesService(this.kodein) }
}
val notesService by kodein.instance<NotesService>()
@Benchmark(threads = 1, iterations = 30, warmup = true, warmupIterations = 1000, timeUnit = TimeUnit.MILLIS)
class RetrieveNotesBenchmarkTest : BenchmarkTest {
override fun execute() {
notesService.getNotes(15)
}
@Test
fun compute() {
val benchmarkResult = BenchmarkRunner(RetrieveNotesBenchmarkTest::class.java).run()
assertTrue(benchmarkResult.median(2, TimeUnit.MILLIS))
assertTrue(benchmarkResult.maxTime(4, TimeUnit.MILLIS))
}
}

View File

@ -0,0 +1,88 @@
package services
import be.vandewalleh.migrations.Migration
import be.vandewalleh.services.NotesService
import be.vandewalleh.services.UserService
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import me.liuwj.ktorm.database.*
import org.amshove.kluent.*
import org.junit.jupiter.api.*
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.singleton
import org.testcontainers.containers.MariaDBContainer
import javax.sql.DataSource
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
class UserServiceTest {
class KMariadbContainer : MariaDBContainer<KMariadbContainer>()
private val mariadb = KMariadbContainer().apply {
this.start()
}
private val hikariConfig = HikariConfig().apply {
jdbcUrl = mariadb.jdbcUrl
username = mariadb.username
password = mariadb.password
}
private val dataSource = HikariDataSource(hikariConfig)
private val db = Database.Companion.connect(dataSource)
private val kodein = Kodein {
bind<DataSource>() with singleton { dataSource }
bind<Database>() with singleton { db }
bind<Migration>() with singleton { Migration(this.kodein) }
bind<UserService>() with singleton { UserService(this.kodein) }
bind<NotesService>() with singleton { NotesService(this.kodein) }
}
private val migration by kodein.instance<Migration>()
init {
migration.migrate()
}
private val userService by kodein.instance<UserService>()
@Test
@Order(1)
fun `test create user`() {
val username = "hubert"
val email = "a@a"
val password = "password"
println(userService.createUser(username, email, password))
val id = userService.getUserId(email)
id `should not be` null
userService.getUserInfo(id!!)!!.let {
it.username `should be equal to` username
it.email `should be equal to` email
}
}
@Test
@Order(2)
fun `test create same user`() {
userService.createUser(username = "hubert", hashedPassword = "password", email = "a@a") `should be` null
}
@Test
@Order(3)
fun `test delete user`() {
val email = "a@a"
val id = userService.getUserId(email)!!
userService.deleteUser(id)
userService.getUserId(email) `should be` null
userService.getUserInfo(id) `should be` null
}
}