Merge http4k

This commit is contained in:
2020-08-13 19:37:39 +02:00
parent b41b2103f0
commit 24aabd494e
176 changed files with 4965 additions and 8607 deletions
+62
View File
@@ -0,0 +1,62 @@
<project>
<parent>
<artifactId>parent</artifactId>
<groupId>be.simplenotes</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>persistance</artifactId>
<dependencies>
<dependency>
<groupId>be.simplenotes</groupId>
<artifactId>domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>be.simplenotes</groupId>
<artifactId>shared</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>be.simplenotes</groupId>
<artifactId>shared</artifactId>
<version>1.0-SNAPSHOT</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>6.5.2</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-support-mysql</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,13 @@
package be.simplenotes.persistance
import org.flywaydb.core.Flyway
import javax.sql.DataSource
internal class DbMigrationsImpl(private val dataSource: DataSource) : DbMigrations {
override fun migrate() {
Flyway.configure()
.dataSource(dataSource)
.load()
.migrate()
}
}
@@ -0,0 +1,36 @@
package be.simplenotes.persistance
import be.simplenotes.domain.usecases.repositories.NoteRepository
import be.simplenotes.domain.usecases.repositories.UserRepository
import be.simplenotes.persistance.notes.NoteRepositoryImpl
import be.simplenotes.persistance.users.UserRepositoryImpl
import be.simplenotes.shared.config.DataSourceConfig
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import me.liuwj.ktorm.database.*
import org.koin.dsl.module
import javax.sql.DataSource
interface DbMigrations {
fun migrate()
}
private fun hikariDataSource(conf: DataSourceConfig): HikariDataSource {
val hikariConfig = HikariConfig().also {
it.jdbcUrl = conf.jdbcUrl
it.driverClassName = conf.driverClassName
it.username = conf.username
it.password = conf.password
it.maximumPoolSize = conf.maximumPoolSize
it.connectionTimeout = conf.connectionTimeout
}
return HikariDataSource(hikariConfig)
}
val persistanceModule = module {
single<UserRepository> { UserRepositoryImpl(get()) }
single<NoteRepository> { NoteRepositoryImpl(get()) }
single<DbMigrations> { DbMigrationsImpl(get()) }
single<DataSource> { hikariDataSource(get()) }
single { Database.connect(get<DataSource>()) }
}
@@ -0,0 +1,25 @@
package be.simplenotes.persistance.extensions
import me.liuwj.ktorm.schema.*
import java.nio.ByteBuffer
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Types
import java.util.*
internal class UuidBinarySqlType : SqlType<UUID>(Types.BINARY, typeName = "uuidBinary") {
override fun doGetResult(rs: ResultSet, index: Int): UUID? {
val value = rs.getBytes(index) ?: return null
return ByteBuffer.wrap(value).let { b -> UUID(b.long, b.long) }
}
override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: UUID) {
val bytes = ByteBuffer.allocate(16)
.putLong(parameter.mostSignificantBits)
.putLong(parameter.leastSignificantBits)
.array()
ps.setBytes(index, bytes)
}
}
internal fun <E : Any> BaseTable<E>.uuidBinary(name: String) = registerColumn(name, UuidBinarySqlType())
@@ -0,0 +1,123 @@
package be.simplenotes.persistance.notes
import be.simplenotes.domain.model.Note
import be.simplenotes.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata
import be.simplenotes.domain.usecases.repositories.NoteRepository
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.*
import java.time.LocalDateTime
import java.util.*
import kotlin.collections.HashMap
internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
@Throws(IllegalArgumentException::class)
override fun findAll(userId: Int, limit: Int, offset: Int): List<PersistedNoteMetadata> {
require(limit > 0) { "limit should be positive" }
require(offset >= 0) { "offset should not be negative" }
val notes = db.notes
.filterColumns { listOf(it.uuid, it.title, it.updatedAt) }
.filter { it.userId eq userId }
.sortedByDescending { it.updatedAt }
.take(limit)
.drop(offset)
.toList()
if (notes.isEmpty()) return emptyList()
val uuids = notes.map { note -> note.uuid }
val tagsByUuid = db.tags
.filterColumns { listOf(it.noteUuid, it.name) }
.filter { it.noteUuid inList uuids }
.groupByTo(HashMap(), { it.note.uuid }, { it.name })
return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList()
note.toPersistedMetadata(tags)
}
}
override fun exists(userId: Int, uuid: UUID): Boolean {
return db.notes.any { (it.userId eq userId) and (it.uuid eq uuid) }
}
override fun create(userId: Int, note: Note): PersistedNote {
val uuid = UUID.randomUUID()
val entity = note.toEntity(uuid, userId).apply {
this.updatedAt = LocalDateTime.now()
}
db.useTransaction {
db.notes.add(entity)
db.batchInsert(Tags) {
note.meta.tags.forEach { tagName ->
item {
it.noteUuid to uuid
it.name to tagName
}
}
}
}
return entity.toPersistedNote(note.meta.tags)
}
@Suppress("UNCHECKED_CAST")
override fun find(userId: Int, uuid: UUID): PersistedNote? {
val note = db.notes
.filterColumns { it.columns - it.userId }
.filter { it.uuid eq uuid }
.find { it.userId eq userId }
?: return null
val tags = db.tags
.filter { it.noteUuid eq uuid }
.mapColumns { it.name } as List<String>
return note.toPersistedNote(tags)
}
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
db.useTransaction {
val currentNote = db.notes
.find { it.uuid eq uuid and (it.userId eq userId) }
?: return null
currentNote.title = note.meta.title
currentNote.markdown = note.markdown
currentNote.html = note.html
currentNote.updatedAt = LocalDateTime.now()
currentNote.flushChanges()
// delete all tags
db.delete(Tags) {
it.noteUuid eq uuid
}
// put new ones
note.meta.tags.forEach { tagName ->
db.insert(Tags) {
it.name to tagName
it.noteUuid to uuid
}
}
return currentNote.toPersistedNote(note.meta.tags)
}
}
override fun delete(userId: Int, uuid: UUID): Boolean = db.useTransaction {
db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1
}
@Suppress("UNCHECKED_CAST")
override fun getTags(userId: Int): List<String> {
return db.sequenceOf(Tags)
.filter { it.note.userId eq userId }
.mapColumns(isDistinct = true) { it.name } as List<String>
}
override fun count(userId: Int) = db.notes.count { it.userId eq userId }
}
@@ -0,0 +1,58 @@
package be.simplenotes.persistance.notes
import be.simplenotes.domain.model.Note
import be.simplenotes.domain.model.NoteMetadata
import be.simplenotes.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata
import be.simplenotes.persistance.extensions.uuidBinary
import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.persistance.users.Users
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.schema.*
import java.time.Instant
import java.time.LocalDateTime
import java.util.*
internal open class Notes(alias: String?) : Table<NoteEntity>("Notes", alias) {
companion object : Notes(null)
override fun aliased(alias: String) = Notes(alias)
val uuid = uuidBinary("uuid").primaryKey().bindTo { it.uuid }
val title = varchar("title").bindTo { it.title }
val markdown = text("markdown").bindTo { it.markdown }
val html = text("html").bindTo { it.html }
val userId = int("user_id").references(Users) { it.user }
val updatedAt = datetime("updated_at").bindTo { it.updatedAt }
val user get() = userId.referenceTable as Users
}
internal val Database.notes get() = this.sequenceOf(Notes, withReferences = false)
internal interface NoteEntity : Entity<NoteEntity> {
companion object : Entity.Factory<NoteEntity>()
var uuid: UUID
var title: String
var markdown: String
var html: String
var updatedAt: LocalDateTime
var user: UserEntity
}
internal fun NoteEntity.toPersistedMetadata(tags: List<String>) = PersistedNoteMetadata(title, tags, updatedAt, uuid)
internal fun NoteEntity.toPersistedNote(tags: List<String>) = PersistedNote(NoteMetadata(title, tags), markdown, html, updatedAt, uuid)
internal fun Note.toEntity(uuid: UUID, userId: Int): NoteEntity {
val note = this
return NoteEntity {
this.title = note.meta.title
this.markdown = note.markdown
this.html = note.html
this.uuid = uuid
this.user["id"] = userId
}
}
+27
View File
@@ -0,0 +1,27 @@
package be.simplenotes.persistance.notes
import be.simplenotes.persistance.extensions.uuidBinary
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.schema.*
internal open class Tags(alias: String?) : Table<TagEntity>("Tags", alias) {
companion object : Tags(null)
override fun aliased(alias: String) = Tags(alias)
val id = int("id").primaryKey().bindTo { it.id }
val name = varchar("name").bindTo { it.name }
val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
val note get() = noteUuid.referenceTable as Notes
}
internal val Database.tags get() = this.sequenceOf(Tags, withReferences = false)
internal interface TagEntity : Entity<TagEntity> {
companion object : Entity.Factory<TagEntity>()
val id: Int
var name: String
var note: NoteEntity
}
@@ -0,0 +1,26 @@
package be.simplenotes.persistance.users
import be.simplenotes.domain.model.PersistedUser
import be.simplenotes.domain.model.User
import be.simplenotes.domain.usecases.repositories.UserRepository
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.*
import java.sql.SQLIntegrityConstraintViolationException
internal class UserRepositoryImpl(private val db: Database) : UserRepository {
override fun create(user: User): PersistedUser? {
return try {
db.useTransaction { db.users.add(user.toEntity()) }
find(user.username)
} catch (e: SQLIntegrityConstraintViolationException) {
null
}
}
override fun find(username: String) = db.users.find { it.username eq username }?.toPersistedUser()
override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser()
override fun exists(username: String) = db.users.any { it.username eq username }
override fun exists(id: Int) = db.users.any { it.id eq id }
override fun delete(id: Int) = db.useTransaction { db.users.find { it.id eq id }?.delete() == 1 }
}
@@ -0,0 +1,45 @@
package be.simplenotes.persistance.users
import be.simplenotes.domain.model.PersistedUser
import be.simplenotes.domain.model.User
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.schema.*
internal open class Users(alias: String?) : Table<UserEntity>("Users", alias) {
companion object : Users(null)
override fun aliased(alias: String) = Users(alias)
val id = int("id").primaryKey().bindTo { it.id }
val username = varchar("username").bindTo { it.username }
val password = varchar("password").bindTo { it.password }
}
internal interface UserEntity : Entity<UserEntity> {
companion object : Entity.Factory<UserEntity>()
val id: Int
var username: String
var password: String
}
internal fun UserEntity.toPersistedUser() = PersistedUser(username, password, id)
internal fun PersistedUser.toEntity(): UserEntity {
val user = this
return UserEntity {
this.username = user.username
this.password = user.password
}.apply { this["id"] = user.id }
}
internal fun User.toEntity(): UserEntity {
val user = this
return UserEntity {
this.username = user.username
this.password = user.password
}
}
internal val Database.users get() = this.sequenceOf(Users, withReferences = false)
@@ -0,0 +1,33 @@
create table Users
(
id int auto_increment primary key,
username varchar(50) not null,
password varchar(255) not null,
constraint username unique (username)
);
create table Notes
(
uuid binary(16) not null primary key,
title varchar(50) not null,
markdown mediumtext not null,
html mediumtext not null,
user_id int not null,
updated_at datetime null,
constraint Notes_fk_user foreign key (user_id) references Users (id) on delete cascade
);
create index user_id on Notes (user_id);
create table Tags
(
id int auto_increment primary key,
name varchar(50) not null,
note_uuid binary(16) not null,
constraint Tags_fk_note foreign key (note_uuid) references Notes (uuid) on delete cascade
);
create index note_uuid on Tags (note_uuid);
@@ -0,0 +1,16 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<encoder>
<pattern>%cyan(%d{YYYY-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %green(%logger{36}) - %msg%n
</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="WARN"/>
<logger name="me.liuwj.ktorm.database" level="INFO"/>
<logger name="com.zaxxer.hikari" level="INFO"/>
<logger name="org.flywaydb.core" level="INFO"/>
</configuration>
+1
View File
@@ -0,0 +1 @@
package be.simplenotes.persistance
@@ -0,0 +1,272 @@
package be.simplenotes.persistance.notes
import be.simplenotes.domain.model.*
import be.simplenotes.domain.usecases.repositories.NoteRepository
import be.simplenotes.domain.usecases.repositories.UserRepository
import be.simplenotes.persistance.DbMigrations
import be.simplenotes.persistance.persistanceModule
import be.simplenotes.shared.config.DataSourceConfig
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.*
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.flywaydb.core.Flyway
import org.junit.jupiter.api.*
import org.junit.jupiter.api.parallel.ResourceLock
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import java.sql.SQLIntegrityConstraintViolationException
import java.util.*
import javax.sql.DataSource
@ResourceLock("h2")
internal class NoteRepositoryImplTest {
private val testModule = module {
single { dataSourceConfig() }
}
private val koinApp = koinApplication {
modules(persistanceModule, testModule)
}
private fun dataSourceConfig() = DataSourceConfig(
jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;",
driverClassName = "org.h2.Driver",
username = "h2",
password = "",
maximumPoolSize = 2,
connectionTimeout = 3000
)
private val koin = koinApp.koin
@AfterAll
fun afterAll() = koinApp.close()
private val migration = koin.get<DbMigrations>()
private val dataSource = koin.get<DataSource>()
private val noteRepo = koin.get<NoteRepository>()
private val userRepo = koin.get<UserRepository>()
private val db = koin.get<Database>()
private lateinit var user1: PersistedUser
private lateinit var user2: PersistedUser
@BeforeEach
fun beforeEach() {
Flyway.configure()
.dataSource(dataSource)
.load()
.clean()
migration.migrate()
user1 = userRepo.create(User("1", "1"))!!
user2 = userRepo.create(User("2", "2"))!!
}
private fun createNote(
userId: Int,
title: String,
tags: List<String> = emptyList(),
md: String = "md",
html: String = "html",
): PersistedNote = noteRepo.create(userId, Note(NoteMetadata(title, tags), md, html))
private fun PersistedNote.toPersistedMeta() = PersistedNoteMetadata(meta.title, meta.tags, updatedAt, uuid)
@Nested
@DisplayName("create()")
inner class Create {
@Test
fun `create note for non existing user`() {
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
assertThatThrownBy {
noteRepo.create(1000, note)
}.isInstanceOf(SQLIntegrityConstraintViolationException::class.java)
}
@Test
fun `create note for existing user`() {
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
assertThat(noteRepo.create(user1.id, note))
.isEqualToIgnoringGivenFields(note, "uuid", "updatedAt")
.hasNoNullFieldsOrProperties()
assertThat(db.notes.toList())
.hasSize(1)
.first()
.isEqualToIgnoringGivenFields(note, "uuid", "updatedAt")
}
}
@Nested
@DisplayName("findAll()")
inner class FindAll {
@Test
fun `find all notes`() {
val notes1 = listOf(
createNote(user1.id, "1", listOf("a", "b")),
createNote(user1.id, "2"),
createNote(user1.id, "3", listOf("c"))
)
val notes2 = listOf(
createNote(user2.id, "4")
)
assertThat(noteRepo.findAll(user1.id))
.hasSize(3)
.usingElementComparatorIgnoringFields("updatedAt")
.containsExactlyInAnyOrderElementsOf(
notes1.map { it.toPersistedMeta() }
)
assertThat(noteRepo.findAll(user2.id))
.hasSize(1)
.usingElementComparatorIgnoringFields("updatedAt")
.containsExactlyInAnyOrderElementsOf(
notes2.map { it.toPersistedMeta() }
)
assertThat(noteRepo.findAll(1000)).isEmpty()
}
@Test
fun pagination() {
(50 downTo 1).forEach {
createNote(user1.id, "$it")
}
assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 0))
.hasSize(20)
.allMatch { it.title.toInt() in 1..20 }
assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 20))
.hasSize(20)
.allMatch { it.title.toInt() in 21..40 }
assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 40))
.hasSize(10)
.allMatch { it.title.toInt() in 41..50 }
}
}
@Nested
@DisplayName("find() | exists()")
inner class FindExists {
@Test
@Suppress("UNCHECKED_CAST")
fun `find an existing note`() {
createNote(user1.id, "1", listOf("a", "b"))
val note = db.notes.find { it.title eq "1" }!!
.let { entity ->
val tags = db.tags.filter { it.noteUuid eq entity.uuid }.mapColumns { it.name } as List<String>
entity.toPersistedNote(tags)
}
assertThat(noteRepo.find(user1.id, note.uuid))
.isEqualTo(note)
assertThat(noteRepo.exists(user1.id, note.uuid))
.isTrue
}
@Test
fun `find an existing note from the wrong user`() {
val note = createNote(user1.id, "1", listOf("a", "b"))
assertThat(noteRepo.find(user2.id, note.uuid)).isNull()
assertThat(noteRepo.exists(user2.id, note.uuid)).isFalse
}
@Test
fun `find a non existing note`() {
createNote(user1.id, "1", listOf("a", "b"))
val uuid = UUID.randomUUID()
assertThat(noteRepo.find(user1.id, uuid)).isNull()
assertThat(noteRepo.exists(user2.id, uuid)).isFalse
}
}
@Nested
@DisplayName("delete()")
inner class Delete {
@Test
fun `delete an existing note for a user should succeed and then fail`() {
val note = createNote(user1.id, "1", listOf("a", "b"))
assertThat(noteRepo.delete(user1.id, note.uuid))
.isTrue
assertThat(noteRepo.delete(user1.id, note.uuid))
.isFalse
}
@Test
fun `delete an existing note for the wrong user`() {
val note = createNote(user1.id, "1", listOf("a", "b"))
assertThat(noteRepo.delete(1000, note.uuid))
.isFalse
}
}
@Nested
@DisplayName("getTags()")
inner class Tags {
@Test
fun getTags() {
val notes1 = listOf(
createNote(user1.id, "1", listOf("a", "b")),
createNote(user1.id, "2"),
createNote(user1.id, "3", listOf("c", "a"))
)
val notes2 = listOf(
createNote(user2.id, "4", listOf("a"))
)
val user1Tags = notes1.flatMap { it.meta.tags }.toSet()
assertThat(noteRepo.getTags(user1.id))
.containsExactlyInAnyOrderElementsOf(user1Tags)
val user2Tags = notes2.flatMap { it.meta.tags }.toSet()
assertThat(noteRepo.getTags(user2.id))
.containsExactlyInAnyOrderElementsOf(user2Tags)
assertThat(noteRepo.getTags(1000))
.isEmpty()
}
}
@Nested
@DisplayName("update()")
inner class Update {
@Test
fun getTags() {
val note1 = createNote(user1.id, "1", listOf("a", "b"))
val newNote1 = Note(meta = note1.meta, markdown = "new", "new")
assertThat(noteRepo.update(user1.id, note1.uuid, newNote1))
.isNotNull
assertThat(noteRepo.find(user1.id, note1.uuid))
.isEqualToComparingOnlyGivenFields(newNote1, "meta", "markdown", "html")
val note2 = createNote(user1.id, "2")
val newNote2 = Note(meta = note1.meta.copy(tags = listOf("a")), markdown = "new", "new")
assertThat(noteRepo.update(user1.id, note2.uuid, newNote2))
.isNotNull
assertThat(noteRepo.find(user1.id, note2.uuid))
.isEqualToComparingOnlyGivenFields(newNote2, "meta", "markdown", "html")
}
}
}
@@ -0,0 +1,116 @@
package be.simplenotes.persistance.users
import be.simplenotes.domain.model.User
import be.simplenotes.domain.usecases.repositories.UserRepository
import be.simplenotes.persistance.DbMigrations
import be.simplenotes.persistance.persistanceModule
import be.simplenotes.shared.config.DataSourceConfig
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.*
import org.assertj.core.api.Assertions.assertThat
import org.flywaydb.core.Flyway
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.parallel.ResourceLock
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import javax.sql.DataSource
@ResourceLock("h2")
internal class UserRepositoryImplTest {
// region setup
private val testModule = module {
single { dataSourceConfig() }
}
private val koinApp = koinApplication {
modules(persistanceModule, testModule)
}
private fun dataSourceConfig() = DataSourceConfig(
jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;",
driverClassName = "org.h2.Driver",
username = "h2",
password = "",
maximumPoolSize = 2,
connectionTimeout = 3000
)
private val koin = koinApp.koin
@AfterAll
fun afterAll() = koinApp.close()
private val migration = koin.get<DbMigrations>()
private val dataSource = koin.get<DataSource>()
private val userRepo = koin.get<UserRepository>()
private val db = koin.get<Database>()
@BeforeEach
fun beforeEach() {
Flyway.configure()
.dataSource(dataSource)
.load()
.clean()
migration.migrate()
}
// endregion setup
@Test
fun `insert user`() {
val user = User("username", "test")
assertThat(userRepo.create(user)).isNotNull
assertThat(db.users.find { it.username eq user.username }).isNotNull
assertThat(db.users.toList()).hasSize(1)
assertThat(userRepo.create(user)).isNull()
}
@Nested
inner class Query {
@Test
fun `query existing user`() {
val user = User("username", "test")
userRepo.create(user)
val foundUserMaybe = userRepo.find(user.username)
assertThat(foundUserMaybe).isNotNull
val foundUser = foundUserMaybe!!
assertThat(foundUser).isEqualToIgnoringGivenFields(user, "id")
assertThat(userRepo.exists(user.username)).isTrue
assertThat(userRepo.find(foundUser.id)).isEqualTo(foundUser)
assertThat(userRepo.exists(foundUser.id)).isTrue
}
@Test
fun `query non existing user`() {
assertThat(userRepo.find("I don't exist")).isNull()
assertThat(userRepo.find(1)).isNull()
assertThat(userRepo.exists(1)).isFalse
assertThat(userRepo.exists("I don't exist")).isFalse
}
}
@Nested
inner class Delete {
@Test
fun `delete existing user`() {
val user = User("username", "test")
userRepo.create(user)
val foundUser = userRepo.find(user.username)!!
assertThat(userRepo.delete(foundUser.id)).isTrue
assertThat(db.users.toList()).isEmpty()
}
@Test
fun `delete non existing user`() {
assertThat(userRepo.delete(1)).isFalse
}
}
}