diff --git a/api/pom.xml b/api/pom.xml index 098b0a5..4cc60f5 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -161,6 +161,24 @@ 1.0.2 test + + io.ktor + ktor-server-tests + ${ktor_version} + test + + + org.testcontainers + mariadb + 1.14.3 + test + + + org.amshove.kluent + kluent + 1.61 + test + ${project.basedir}/src @@ -174,12 +192,7 @@ org.apache.maven.plugins maven-surefire-plugin - - - Test* - *Test - - + 3.0.0-M4 maven-compiler-plugin @@ -210,7 +223,7 @@ enable - ${project.basedir}/src/test + ${project.basedir}/test diff --git a/api/resources/db/migration/V7__Update_constraints_cascade_delete.sql b/api/resources/db/migration/V7__Update_constraints_cascade_delete.sql index 48bb1f5..ed821bf 100644 --- a/api/resources/db/migration/V7__Update_constraints_cascade_delete.sql +++ b/api/resources/db/migration/V7__Update_constraints_cascade_delete.sql @@ -1,19 +1,19 @@ -- ON DELETE -> CASCADE ALTER TABLE `Notes` - DROP CONSTRAINT `Notes_ibfk_1`; + DROP CONSTRAINT IF EXISTS `Notes_ibfk_1`; ALTER TABLE `Notes` ADD FOREIGN KEY (`user_id`) REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; ALTER TABLE `Chapters` - DROP CONSTRAINT `Chapters_ibfk_1`; + DROP CONSTRAINT IF EXISTS `Chapters_ibfk_1`; ALTER TABLE `Chapters` ADD FOREIGN KEY (`note_id`) REFERENCES `Notes` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; ALTER TABLE `Tags` - DROP CONSTRAINT `Tags_ibfk_1`; + DROP CONSTRAINT IF EXISTS `Tags_ibfk_1`; ALTER TABLE `Tags` - ADD FOREIGN KEY (`note_id`) REFERENCES `Notes` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; \ No newline at end of file + ADD FOREIGN KEY (`note_id`) REFERENCES `Notes` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT; diff --git a/api/resources/logback.xml b/api/resources/logback.xml index d38ae26..b9c2bba 100644 --- a/api/resources/logback.xml +++ b/api/resources/logback.xml @@ -12,4 +12,6 @@ + + diff --git a/api/src/routing/UserController.kt b/api/src/routing/UserController.kt index f8f94e3..05245ae 100644 --- a/api/src/routing/UserController.kt +++ b/api/src/routing/UserController.kt @@ -2,7 +2,6 @@ package be.vandewalleh.routing import be.vandewalleh.extensions.respondStatus import be.vandewalleh.extensions.userId -import be.vandewalleh.services.UserDto import be.vandewalleh.services.UserService import io.ktor.application.* import io.ktor.auth.* @@ -26,9 +25,7 @@ fun Routing.user(kodein: Kodein) { val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt()) - userService.createUser( - UserDto(user.username, user.email, hashedPassword) - ) + userService.createUser(user.username, user.email, hashedPassword) call.respondStatus(HttpStatusCode.Created) } @@ -42,10 +39,7 @@ fun Routing.user(kodein: Kodein) { val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt()) - userService.updateUser( - call.userId(), - UserDto(user.username, user.email, hashedPassword) - ) + userService.updateUser(call.userId(), user.username, user.email, hashedPassword) call.respondStatus(HttpStatusCode.OK) } @@ -57,5 +51,6 @@ fun Routing.user(kodein: Kodein) { } } +} -} \ No newline at end of file +private data class UserDto(val username: String, val email: String, val password: String) diff --git a/api/src/services/UserService.kt b/api/src/services/UserService.kt index 0ed10f9..8a5f05a 100644 --- a/api/src/services/UserService.kt +++ b/api/src/services/UserService.kt @@ -8,6 +8,7 @@ import me.liuwj.ktorm.entity.* import org.kodein.di.Kodein import org.kodein.di.KodeinAware import org.kodein.di.generic.instance +import java.sql.SQLIntegrityConstraintViolationException 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) .select(Users.email, Users.password, Users.id) .where { Users.username eq username } .map { row -> - UserSchema( - row[Users.id]!!, - username, - row[Users.email]!!, - row[Users.password]!! - ) + Users.createEntity(row) } .firstOrNull() } @@ -59,11 +55,11 @@ class UserService(override val kodein: Kodein) : KodeinAware { .firstOrNull() != null } - fun getUserInfo(id: Int): UserInfoDto? { + fun getUserInfo(id: Int): User? { return db.from(Users) .select(Users.email, Users.username) .where { Users.id eq id } - .map { UserInfoDto(it[Users.username]!!, it[Users.email]!!) } + .map { Users.createEntity(it) } .firstOrNull() } @@ -71,25 +67,30 @@ class UserService(override val kodein: Kodein) : KodeinAware { * create a new user * password should already be hashed */ - fun createUser(user: UserDto) { - db.useTransaction { - val newUser = User { - this.username = user.username - this.email = user.email - this.password = user.password - this.createdAt = LocalDateTime.now() - } + fun createUser(username: String, email: String, hashedPassword: String): User? { + try { + db.useTransaction { + val newUser = User { + this.username = username + this.email = email + 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.update(Users) { - it.username to user.username - it.email to user.email - it.password to user.password + it.username to username + it.email to email + it.password to hashedPassword where { 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) diff --git a/api/test/FakeDataTest.kt b/api/test/FakeDataTest.kt deleted file mode 100644 index 3d8f6a4..0000000 --- a/api/test/FakeDataTest.kt +++ /dev/null @@ -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() - - @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) - } - -} diff --git a/api/test/NotesRetrievePerformanceTest.kt b/api/test/NotesRetrievePerformanceTest.kt deleted file mode 100644 index 35fe610..0000000 --- a/api/test/NotesRetrievePerformanceTest.kt +++ /dev/null @@ -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() with singleton { db } - bind() with singleton { UserService(this.kodein) } - bind() with singleton { NotesService(this.kodein) } -} - -val notesService by kodein.instance() - -@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)) - } - -} diff --git a/api/test/services/UserServiceTest.kt b/api/test/services/UserServiceTest.kt new file mode 100644 index 0000000..3cfc9a4 --- /dev/null +++ b/api/test/services/UserServiceTest.kt @@ -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() + + 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() with singleton { dataSource } + bind() with singleton { db } + bind() with singleton { Migration(this.kodein) } + bind() with singleton { UserService(this.kodein) } + bind() with singleton { NotesService(this.kodein) } + } + + private val migration by kodein.instance() + + init { + migration.migrate() + } + + private val userService by kodein.instance() + + @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 + } +}