Flatten packages

Remove modules prefix
This commit is contained in:
2020-11-11 22:32:23 +01:00
parent e6a7af840a
commit 8439782430
155 changed files with 51 additions and 33 deletions
+52
View File
@@ -0,0 +1,52 @@
import be.simplenotes.Libs
plugins {
id("be.simplenotes.base")
kotlin("kapt")
`java-test-fixtures`
}
dependencies {
implementation(project(":types"))
implementation(project(":config"))
implementation(Libs.mariadbClient)
implementation(Libs.h2)
implementation(Libs.flywayCore)
implementation(Libs.hikariCP)
implementation(Libs.ktormCore)
implementation(Libs.ktormMysql)
implementation(Libs.logbackClassic)
compileOnly(Libs.mapstruct)
kapt(Libs.mapstructProcessor)
implementation(Libs.micronaut)
kapt(Libs.micronautProcessor)
testImplementation(Libs.junit)
testImplementation(Libs.assertJ)
testImplementation(Libs.logbackClassic)
testImplementation(Libs.mariaTestContainer)
testFixturesImplementation(project(":types"))
testFixturesImplementation(project(":config"))
testFixturesImplementation(project(":persistance"))
testFixturesImplementation(Libs.micronaut)
kaptTestFixtures(Libs.micronautProcessor)
testFixturesImplementation(Libs.faker) {
exclude(group = "org.yaml")
}
testFixturesImplementation(Libs.snakeyaml)
testFixturesImplementation(Libs.mariaTestContainer)
testFixturesImplementation(Libs.flywayCore)
testFixturesImplementation(Libs.junit)
testFixturesImplementation(Libs.ktormCore)
testFixturesImplementation(Libs.hikariCP)
}
kotlin.sourceSets["testFixtures"].kotlin.srcDirs("testfixtures")
@@ -0,0 +1,36 @@
create table Users
(
id int auto_increment primary key,
username varchar(50) not null,
password varchar(255) not null,
constraint username unique (username)
) character set 'utf8mb4'
collate 'utf8mb4_general_ci';
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
) character set 'utf8mb4'
collate 'utf8mb4_general_ci';
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
) character set 'utf8mb4'
collate 'utf8mb4_general_ci';
create index note_uuid on Tags (note_uuid);
@@ -0,0 +1,2 @@
alter table Notes
add column deleted bool not null default false
@@ -0,0 +1,2 @@
alter table Notes
add column public bool not null default 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,2 @@
alter table Notes
add column deleted bool not null default false
@@ -0,0 +1,2 @@
alter table Notes
add column public bool not null default false
+31
View File
@@ -0,0 +1,31 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.utils.DbType
import be.simplenotes.persistance.utils.type
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.database.asIterable
import me.liuwj.ktorm.database.use
import java.sql.SQLException
import javax.inject.Singleton
interface DbHealthCheck {
fun isOk(): Boolean
}
@Singleton
internal class DbHealthCheckImpl(
private val db: Database,
private val dataSourceConfig: DataSourceConfig,
) : DbHealthCheck {
override fun isOk() = if (dataSourceConfig.type() == DbType.H2) true
else try {
db.useConnection { connection ->
connection.prepareStatement("""SHOW DATABASES""").use {
it.executeQuery().asIterable().map { it.getString(1) }
}
}.any { it in dataSourceConfig.jdbcUrl }
} catch (e: SQLException) {
false
}
}
+32
View File
@@ -0,0 +1,32 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.utils.DbType
import be.simplenotes.persistance.utils.type
import org.flywaydb.core.Flyway
import javax.inject.Singleton
import javax.sql.DataSource
interface DbMigrations {
fun migrate()
}
@Singleton
internal class DbMigrationsImpl(
private val dataSource: DataSource,
private val dataSourceConfig: DataSourceConfig,
) : DbMigrations {
override fun migrate() {
val migrationDir = when (dataSourceConfig.type()) {
DbType.H2 -> "db/migration/other"
DbType.MariaDb -> "db/migration/mariadb"
}
Flyway.configure()
.dataSource(dataSource)
.locations(migrationDir)
.load()
.migrate()
}
}
+34
View File
@@ -0,0 +1,34 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory
import me.liuwj.ktorm.database.Database
import javax.inject.Singleton
import javax.sql.DataSource
@Factory
class PersistanceModule {
@Singleton
internal fun database(migrations: DbMigrations, dataSource: DataSource): Database {
migrations.migrate()
return Database.connect(dataSource)
}
@Singleton
@Bean(preDestroy = "close")
internal fun dataSource(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)
}
}
@@ -0,0 +1,85 @@
package be.simplenotes.persistance.converters
import be.simplenotes.persistance.notes.NoteEntity
import be.simplenotes.types.*
import me.liuwj.ktorm.entity.Entity
import org.mapstruct.Mapper
import org.mapstruct.Mapping
import org.mapstruct.Mappings
import org.mapstruct.ReportingPolicy
import java.time.LocalDateTime
import java.util.*
import javax.inject.Singleton
@Mapper(
uses = [NoteEntityFactory::class, UserEntityFactory::class],
unmappedTargetPolicy = ReportingPolicy.IGNORE,
componentModel = "jsr330"
)
internal abstract class NoteConverter {
fun toNote(entity: NoteEntity, tags: Tags) =
Note(NoteMetadata(title = entity.title, tags = tags), entity.markdown, entity.html)
@Mappings(
Mapping(target = ".", source = "entity"),
Mapping(target = "tags", source = "tags"),
)
abstract fun toNoteMetadata(entity: NoteEntity, tags: Tags): NoteMetadata
@Mappings(
Mapping(target = ".", source = "entity"),
Mapping(target = "tags", source = "tags"),
)
abstract fun toPersistedNoteMetadata(entity: NoteEntity, tags: Tags): PersistedNoteMetadata
fun toPersistedNote(entity: NoteEntity, tags: Tags) = PersistedNote(
NoteMetadata(title = entity.title, tags = tags),
entity.markdown,
entity.html,
entity.updatedAt,
entity.uuid,
entity.public
)
@Mappings(
Mapping(target = ".", source = "entity"),
Mapping(target = "trash", source = "entity.deleted"),
Mapping(target = "tags", source = "tags"),
)
abstract fun toExportedNote(entity: NoteEntity, tags: Tags): ExportedNote
fun toEntity(note: Note, uuid: UUID, userId: Int, updatedAt: LocalDateTime) = NoteEntity {
this.title = note.meta.title
this.markdown = note.markdown
this.html = note.html
this.uuid = uuid
this.deleted = false
this.public = false
this.user.id = userId
this.updatedAt = updatedAt
}
@Mappings(
Mapping(target = ".", source = "note"),
Mapping(target = "updatedAt", source = "updatedAt"),
Mapping(target = "uuid", source = "uuid"),
Mapping(target = "public", constant = "false"),
)
abstract fun toPersistedNote(note: Note, updatedAt: LocalDateTime, uuid: UUID): PersistedNote
abstract fun toEntity(persistedNoteMetadata: PersistedNoteMetadata): NoteEntity
abstract fun toEntity(noteMetadata: NoteMetadata): NoteEntity
@Mapping(target = "title", source = "meta.title")
abstract fun toEntity(persistedNote: PersistedNote): NoteEntity
@Mapping(target = "deleted", source = "trash")
abstract fun toEntity(exportedNote: ExportedNote): NoteEntity
}
typealias Tags = List<String>
@Singleton
internal class NoteEntityFactory : Entity.Factory<NoteEntity>()
@@ -0,0 +1,24 @@
package be.simplenotes.persistance.converters
import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User
import me.liuwj.ktorm.entity.Entity
import org.mapstruct.Mapper
import org.mapstruct.ReportingPolicy
import javax.inject.Singleton
@Mapper(
uses = [UserEntityFactory::class],
unmappedTargetPolicy = ReportingPolicy.IGNORE,
componentModel = "jsr330"
)
internal interface UserConverter {
fun toUser(userEntity: UserEntity): User
fun toPersistedUser(userEntity: UserEntity): PersistedUser
fun toEntity(user: User): UserEntity
fun toEntity(user: PersistedUser): UserEntity
}
@Singleton
internal class UserEntityFactory : Entity.Factory<UserEntity>()
@@ -0,0 +1,26 @@
package be.simplenotes.persistance.extensions
import me.liuwj.ktorm.schema.BaseTable
import me.liuwj.ktorm.schema.SqlType
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())
+219
View File
@@ -0,0 +1,219 @@
package be.simplenotes.persistance.notes
import be.simplenotes.persistance.converters.NoteConverter
import be.simplenotes.persistance.repositories.NoteRepository
import be.simplenotes.types.ExportedNote
import be.simplenotes.types.Note
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.*
import java.time.LocalDateTime
import java.util.*
import javax.inject.Singleton
import kotlin.collections.HashMap
@Singleton
internal class NoteRepositoryImpl(
private val db: Database,
private val converter: NoteConverter,
) : NoteRepository {
@Throws(IllegalArgumentException::class)
override fun findAll(
userId: Int,
limit: Int,
offset: Int,
tag: String?,
deleted: Boolean,
): List<PersistedNoteMetadata> {
require(limit > 0) { "limit should be positive" }
require(offset >= 0) { "offset should not be negative" }
val uuids1: List<UUID>? = if (tag != null) {
db.from(Tags)
.leftJoin(Notes, on = Notes.uuid eq Tags.noteUuid)
.select(Notes.uuid)
.where { (Notes.userId eq userId) and (Tags.name eq tag) and (Notes.deleted eq deleted) }
.map { it[Notes.uuid]!! }
} else null
var query = db.notes
.filterColumns { listOf(it.uuid, it.title, it.updatedAt) }
.filter { (it.userId eq userId) and (it.deleted eq deleted) }
if (uuids1 != null) query = query.filter { it.uuid inList uuids1 }
val notes = query
.sortedByDescending { it.updatedAt }
.take(limit)
.drop(offset)
.toList()
val tagsByUuid = notes.tagsByUuid()
return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList()
converter.toPersistedNoteMetadata(note, tags)
}
}
override fun exists(userId: Int, uuid: UUID): Boolean {
return db.notes.any { (it.userId eq userId) and (it.uuid eq uuid) and (it.deleted eq false) }
}
override fun create(userId: Int, note: Note): PersistedNote {
val uuid = UUID.randomUUID()
val entity = converter.toEntity(note, uuid, userId, LocalDateTime.now())
db.notes.add(entity)
db.batchInsert(Tags) {
note.meta.tags.forEach { tagName ->
item {
it.noteUuid to uuid
it.name to tagName
}
}
}
return converter.toPersistedNote(entity, note.meta.tags)
}
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) and (it.deleted eq false) }
?: return null
val tags = db.from(Tags)
.select(Tags.name)
.where { Tags.noteUuid eq uuid }
.map { it[Tags.name]!! }
return converter.toPersistedNote(note, tags)
}
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
val now = LocalDateTime.now()
val count = db.update(Notes) {
it.title to note.meta.title
it.markdown to note.markdown
it.html to note.html
it.updatedAt to now
where { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) }
}
if (count == 0) return null
// 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 converter.toPersistedNote(note, now, uuid)
}
override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean {
return if (!permanent) {
db.update(Notes) {
it.deleted to true
it.updatedAt to LocalDateTime.now()
where { it.userId eq userId and (it.uuid eq uuid) }
} == 1
} else
db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1
}
override fun restore(userId: Int, uuid: UUID): Boolean {
return db.update(Notes) {
it.deleted to false
where { (it.userId eq userId) and (it.uuid eq uuid) }
} == 1
}
override fun getTags(userId: Int): List<String> =
db.from(Tags)
.leftJoin(Notes, on = Notes.uuid eq Tags.noteUuid)
.selectDistinct(Tags.name)
.where { (Notes.userId eq userId) and (Notes.deleted eq false) }
.map { it[Tags.name]!! }
override fun count(userId: Int, tag: String?, deleted: Boolean): Int {
return if (tag == null) db.notes.count { (it.userId eq userId) and (Notes.deleted eq deleted) }
else db.sequenceOf(Tags).count {
(it.name eq tag) and (it.note.userId eq userId) and (it.note.deleted eq deleted)
}
}
override fun export(userId: Int): List<ExportedNote> {
val notes = db.notes
.filterColumns { it.columns - it.userId }
.filter { it.userId eq userId }
.sortedByDescending { it.updatedAt }
.toList()
val tagsByUuid = notes.tagsByUuid()
return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList()
converter.toExportedNote(note, tags)
}
}
override fun findAllDetails(userId: Int): List<PersistedNote> {
val notes = db.notes
.filterColumns { it.columns - it.deleted }
.filter { (it.userId eq userId) and (it.deleted eq false) }
.toList()
val tagsByUuid = notes.tagsByUuid()
return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList()
converter.toPersistedNote(note, tags)
}
}
override fun makePublic(userId: Int, uuid: UUID) = db.update(Notes) {
it.public to true
where { Notes.userId eq userId and (Notes.uuid eq uuid) and (it.deleted eq false) }
} == 1
override fun makePrivate(userId: Int, uuid: UUID) = db.update(Notes) {
it.public to false
where { Notes.userId eq userId and (Notes.uuid eq uuid) and (it.deleted eq false) }
} == 1
override fun findPublic(uuid: UUID): PersistedNote? {
val note = db.notes
.filterColumns { it.columns - it.userId }
.filter { it.uuid eq uuid }
.filter { it.public eq true }
.find { it.deleted eq false }
?: return null
val tags = db.from(Tags)
.select(Tags.name)
.where { Tags.noteUuid eq uuid }
.map { it[Tags.name]!! }
return converter.toPersistedNote(note, tags)
}
private fun List<NoteEntity>.tagsByUuid(): Map<UUID, List<String>> {
return if (isEmpty()) emptyMap()
else db.tags
.filterColumns { listOf(it.noteUuid, it.name) }
.filter { it.noteUuid inList map { note -> note.uuid } }
.groupByTo(HashMap(), { it.note.uuid }, { it.name })
}
}
+45
View File
@@ -0,0 +1,45 @@
@file:Suppress("unused")
package be.simplenotes.persistance.notes
import be.simplenotes.persistance.extensions.uuidBinary
import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.persistance.users.Users
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.entity.Entity
import me.liuwj.ktorm.entity.sequenceOf
import me.liuwj.ktorm.schema.*
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 deleted = boolean("deleted").bindTo { it.deleted }
val public = boolean("public").bindTo { it.public }
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 deleted: Boolean
var public: Boolean
var user: UserEntity
}
+30
View File
@@ -0,0 +1,30 @@
package be.simplenotes.persistance.notes
import be.simplenotes.persistance.extensions.uuidBinary
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.entity.Entity
import me.liuwj.ktorm.entity.sequenceOf
import me.liuwj.ktorm.schema.Table
import me.liuwj.ktorm.schema.int
import me.liuwj.ktorm.schema.varchar
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,35 @@
package be.simplenotes.persistance.repositories
import be.simplenotes.types.ExportedNote
import be.simplenotes.types.Note
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
import java.util.*
interface NoteRepository {
fun findAll(
userId: Int,
limit: Int = 20,
offset: Int = 0,
tag: String? = null,
deleted: Boolean = false
): List<PersistedNoteMetadata>
fun count(userId: Int, tag: String? = null, deleted: Boolean = false): Int
fun delete(userId: Int, uuid: UUID, permanent: Boolean = false): Boolean
fun restore(userId: Int, uuid: UUID): Boolean
// These methods only access notes where `Notes.deleted = false`
fun getTags(userId: Int): List<String>
fun exists(userId: Int, uuid: UUID): Boolean
fun create(userId: Int, note: Note): PersistedNote
fun find(userId: Int, uuid: UUID): PersistedNote?
fun update(userId: Int, uuid: UUID, note: Note): PersistedNote?
fun export(userId: Int): List<ExportedNote>
fun findAllDetails(userId: Int): List<PersistedNote>
fun makePublic(userId: Int, uuid: UUID): Boolean
fun makePrivate(userId: Int, uuid: UUID): Boolean
fun findPublic(uuid: UUID): PersistedNote?
}
@@ -0,0 +1,14 @@
package be.simplenotes.persistance.repositories
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User
interface UserRepository {
fun create(user: User): PersistedUser?
fun find(username: String): PersistedUser?
fun find(id: Int): PersistedUser?
fun exists(username: String): Boolean
fun exists(id: Int): Boolean
fun delete(id: Int): Boolean
fun findAll(): List<Int>
}
@@ -0,0 +1,9 @@
package be.simplenotes.persistance.transactions
import me.liuwj.ktorm.database.Database
import javax.inject.Singleton
@Singleton
internal class KtormTransactionService(private val database: Database) : TransactionService {
override fun <T> use(block: () -> T) = database.useTransaction { block() }
}
@@ -0,0 +1,5 @@
package be.simplenotes.persistance.transactions
interface TransactionService {
fun <T> use(block: () -> T): T
}
@@ -0,0 +1,42 @@
package be.simplenotes.persistance.users
import be.simplenotes.persistance.converters.UserConverter
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.any
import me.liuwj.ktorm.entity.find
import java.sql.SQLIntegrityConstraintViolationException
import javax.inject.Singleton
@Singleton
internal class UserRepositoryImpl(
private val db: Database,
private val converter: UserConverter,
) : UserRepository {
override fun create(user: User): PersistedUser? {
return try {
val id = db.insertAndGenerateKey(Users) {
it.username to user.username
it.password to user.password
} as Int
PersistedUser(user.username, user.password, id)
} catch (e: SQLIntegrityConstraintViolationException) {
null
}
}
override fun find(username: String) = db.users.find { it.username eq username }
?.let { converter.toPersistedUser(it) }
override fun find(id: Int) = db.users.find { it.id eq id }?.let {
converter.toPersistedUser(it)
}
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.delete(Users) { it.id eq id } == 1
override fun findAll() = db.from(Users).select(Users.id).map { it[Users.id]!! }
}
+28
View File
@@ -0,0 +1,28 @@
package be.simplenotes.persistance.users
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.entity.Entity
import me.liuwj.ktorm.entity.sequenceOf
import me.liuwj.ktorm.schema.Table
import me.liuwj.ktorm.schema.int
import me.liuwj.ktorm.schema.varchar
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>()
var id: Int
var username: String
var password: String
}
internal val Database.users get() = this.sequenceOf(Users, withReferences = false)
@@ -0,0 +1,8 @@
package be.simplenotes.persistance.utils
import be.simplenotes.config.DataSourceConfig
enum class DbType { H2, MariaDb }
fun DataSourceConfig.type(): DbType = if (jdbcUrl.contains("mariadb")) DbType.MariaDb
else DbType.H2
+24
View File
@@ -0,0 +1,24 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import org.testcontainers.containers.MariaDBContainer
class KMariadbContainer : MariaDBContainer<KMariadbContainer>("mariadb:10.5.5")
fun h2dataSourceConfig() = DataSourceConfig(
jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;",
driverClassName = "org.h2.Driver",
username = "h2",
password = "",
maximumPoolSize = 2,
connectionTimeout = 3000
)
fun mariadbDataSourceConfig(jdbcUrl: String) = DataSourceConfig(
jdbcUrl = jdbcUrl,
driverClassName = "org.mariadb.jdbc.Driver",
username = "test",
password = "test",
maximumPoolSize = 2,
connectionTimeout = 3000
)
+35
View File
@@ -0,0 +1,35 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.parallel.ResourceLock
@ResourceLock("h2")
class H2DbHealthCheckImplTest : DbTest() {
override fun dataSourceConfig() = h2dataSourceConfig()
@Test
fun healthCheck() {
assertThat(beanContext.getBean<DbHealthCheck>().isOk()).isTrue
}
}
@ResourceLock("mariadb")
class MariaDbHealthCheckImplTest : DbTest() {
lateinit var mariaDB: KMariadbContainer
override fun dataSourceConfig(): DataSourceConfig {
mariaDB = KMariadbContainer()
mariaDB.start()
return mariadbDataSourceConfig(mariaDB.jdbcUrl)
}
@Test
fun healthCheck() {
val healthCheck = beanContext.getBean<DbHealthCheck>()
assertThat(healthCheck.isOk()).isTrue
mariaDB.stop()
assertThat(healthCheck.isOk()).isFalse
}
}
+44
View File
@@ -0,0 +1,44 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import io.micronaut.context.BeanContext
import org.flywaydb.core.Flyway
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import javax.sql.DataSource
abstract class DbTest {
abstract fun dataSourceConfig(): DataSourceConfig
val beanContext = BeanContext
.build()
inline fun <reified T> BeanContext.getBean(): T = getBean(T::class.java)
@BeforeAll
fun setComponent() {
beanContext
.registerSingleton(dataSourceConfig())
.start()
}
@BeforeEach
fun beforeEach() {
val migration = beanContext.getBean<DbMigrations>()
val dataSource = beanContext.getBean<DataSource>()
Flyway.configure()
.dataSource(dataSource)
.load()
.clean()
migration.migrate()
}
@AfterAll
fun closeCtx() {
beanContext.close()
}
}
@@ -0,0 +1,164 @@
package be.simplenotes.persistance.converters
import be.simplenotes.persistance.notes.NoteEntity
import be.simplenotes.types.*
import io.micronaut.context.BeanContext
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.time.LocalDateTime
import java.util.*
internal class NoteConverterTest {
private val ctx = BeanContext.run()
val converter = ctx.getBean(NoteConverter::class.java)
@Nested
@DisplayName("Entity -> Models")
inner class EntityToModels {
@Test
fun `convert NoteEntity to Note`() {
val entity = NoteEntity {
title = "title"
markdown = "md"
html = "html"
}
val tags = listOf("a", "b")
val note = converter.toNote(entity, tags)
val expectedNote = Note(
NoteMetadata(
title = "title",
tags = tags,
),
markdown = "md",
html = "html"
)
assertThat(note).isEqualTo(expectedNote)
}
@Test
fun `convert NoteEntity to ExportedNote`() {
val entity = NoteEntity {
title = "title"
markdown = "md"
html = "html"
updatedAt = LocalDateTime.MIN
deleted = true
}
val tags = listOf("a", "b")
val note = converter.toExportedNote(entity, tags)
val expectedNote = ExportedNote(
title = "title",
tags = tags,
markdown = "md",
html = "html",
updatedAt = LocalDateTime.MIN,
trash = true
)
assertThat(note).isEqualTo(expectedNote)
}
@Test
fun `convert NoteEntity to PersistedNoteMetadata`() {
val entity = NoteEntity {
uuid = UUID.randomUUID()
title = "title"
markdown = "md"
html = "html"
updatedAt = LocalDateTime.MIN
deleted = true
}
val tags = listOf("a", "b")
val note = converter.toPersistedNoteMetadata(entity, tags)
val expectedNote = PersistedNoteMetadata(
title = "title",
tags = tags,
updatedAt = LocalDateTime.MIN,
uuid = entity.uuid
)
assertThat(note).isEqualTo(expectedNote)
}
}
@Nested
@DisplayName("Models -> Entity")
inner class ModelsToEntity {
@Test
fun `convert Note to NoteEntity`() {
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
val entity = converter.toEntity(note, UUID.randomUUID(), 2, LocalDateTime.MIN)
assertThat(entity)
.hasFieldOrPropertyWithValue("markdown", "md")
.hasFieldOrPropertyWithValue("html", "html")
.hasFieldOrPropertyWithValue("title", "title")
.hasFieldOrPropertyWithValue("uuid", entity.uuid)
.hasFieldOrPropertyWithValue("updatedAt", LocalDateTime.MIN)
}
@Test
fun `convert PersistedNoteMetadata to NoteEntity`() {
val persistedNoteMetadata =
PersistedNoteMetadata("title", emptyList(), LocalDateTime.MIN, UUID.randomUUID())
val entity = converter.toEntity(persistedNoteMetadata)
assertThat(entity)
.hasFieldOrPropertyWithValue("uuid", persistedNoteMetadata.uuid)
.hasFieldOrPropertyWithValue("updatedAt", persistedNoteMetadata.updatedAt)
.hasFieldOrPropertyWithValue("title", "title")
}
@Test
fun `convert NoteMetadata to NoteEntity`() {
val noteMetadata = NoteMetadata("title", emptyList())
val entity = converter.toEntity(noteMetadata)
assertThat(entity)
.hasFieldOrPropertyWithValue("title", "title")
}
@Test
fun `convert PersistedNote to NoteEntity`() {
val persistedNote = PersistedNote(
NoteMetadata("title", emptyList()),
markdown = "md",
html = "html",
updatedAt = LocalDateTime.MIN,
uuid = UUID.randomUUID(),
public = true
)
val entity = converter.toEntity(persistedNote)
assertThat(entity)
.hasFieldOrPropertyWithValue("title", "title")
.hasFieldOrPropertyWithValue("markdown", "md")
.hasFieldOrPropertyWithValue("uuid", persistedNote.uuid)
.hasFieldOrPropertyWithValue("updatedAt", persistedNote.updatedAt)
.hasFieldOrPropertyWithValue("deleted", false)
.hasFieldOrPropertyWithValue("public", true)
}
@Test
fun `convert ExportedNote to NoteEntity`() {
val exportedNote = ExportedNote(
"title",
emptyList(),
markdown = "md",
html = "html",
updatedAt = LocalDateTime.MIN,
trash = true
)
val entity = converter.toEntity(exportedNote)
assertThat(entity)
.hasFieldOrPropertyWithValue("title", "title")
.hasFieldOrPropertyWithValue("markdown", "md")
.hasFieldOrPropertyWithValue("updatedAt", exportedNote.updatedAt)
.hasFieldOrPropertyWithValue("deleted", true)
}
}
}
@@ -0,0 +1,48 @@
package be.simplenotes.persistance.converters
import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User
import io.micronaut.context.BeanContext
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
internal class UserConverterTest {
private val ctx = BeanContext.run()
private val converter = ctx.getBean(UserConverter::class.java)
@Test
fun `convert UserEntity to User`() {
val entity = UserEntity {
username = "test"
password = "test2"
}.apply {
this["id"] = 2
}
val user = converter.toUser(entity)
assertThat(user).isEqualTo(User("test", "test2"))
}
@Test
fun `convert UserEntity to PersistedUser`() {
val entity = UserEntity {
username = "test"
password = "test2"
}.apply {
this["id"] = 2
}
val user = converter.toPersistedUser(entity)
assertThat(user).isEqualTo(PersistedUser("test", "test2", 2))
}
@Test
fun `convert User to UserEntity`() {
val user = User("test", "test2")
val entity = converter.toEntity(user)
assertThat(entity)
.hasFieldOrPropertyWithValue("username", "test")
.hasFieldOrPropertyWithValue("password", "test2")
}
}
@@ -0,0 +1,329 @@
package be.simplenotes.persistance.notes
import be.simplenotes.persistance.DbTest
import be.simplenotes.persistance.converters.NoteConverter
import be.simplenotes.persistance.repositories.NoteRepository
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.persistance.users.createFakeUser
import be.simplenotes.types.ExportedNote
import be.simplenotes.types.PersistedUser
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.eq
import me.liuwj.ktorm.entity.filter
import me.liuwj.ktorm.entity.find
import me.liuwj.ktorm.entity.mapColumns
import me.liuwj.ktorm.entity.toList
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.*
import java.sql.SQLIntegrityConstraintViolationException
internal abstract class BaseNoteRepositoryImplTest : DbTest() {
private lateinit var noteRepo: NoteRepository
private lateinit var userRepo: UserRepository
private lateinit var db: Database
private lateinit var user1: PersistedUser
private lateinit var user2: PersistedUser
@BeforeEach
fun insertUsers() {
noteRepo = beanContext.getBean()
userRepo = beanContext.getBean()
db = beanContext.getBean()
user1 = userRepo.createFakeUser()!!
user2 = userRepo.createFakeUser()!!
}
@Nested
@DisplayName("create()")
inner class Create {
@Test
fun `create note for non existing user`() {
val note = fakeNote()
assertThatThrownBy {
noteRepo.create(1000, note)
}.isInstanceOf(SQLIntegrityConstraintViolationException::class.java)
}
@Test
fun `create note for existing user`() {
val note = fakeNote()
assertThat(noteRepo.create(user1.id, note))
.isEqualToIgnoringGivenFields(note, "uuid", "updatedAt", "public")
.hasNoNullFieldsOrProperties()
assertThat(db.notes.toList())
.hasSize(1)
.first()
.isEqualToIgnoringGivenFields(note, "uuid", "updatedAt", "public")
}
}
@Nested
@DisplayName("findAll()")
inner class FindAll {
@Test
fun `find all notes`() {
val notes1 = noteRepo.insertFakeNotes(user1, count = 3)
val notes2 = listOf(noteRepo.insertFakeNote(user2))
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()
}
// TODO: datetime -> timestamp migration
@Disabled("Not working with mariadb, inserts are too fast so the updated_at is the same")
@Test
fun pagination() {
(50 downTo 1).forEach { i ->
noteRepo.insertFakeNote(user1, "$i")
}
assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 0).onEach { println(it) })
.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 }
}
@Test
fun `find all notes with tag`() {
with(noteRepo) {
insertFakeNote(user1, "1", listOf("a", "b"))
insertFakeNote(user1, "2", tags = emptyList())
insertFakeNote(user1, "3", listOf("c"))
insertFakeNote(user1, "4", listOf("c"))
insertFakeNote(user2, "5", listOf("c"))
}
assertThat(noteRepo.findAll(user1.id, tag = "a"))
.hasSize(1)
.first()
.hasFieldOrPropertyWithValue("title", "1")
assertThat(noteRepo.findAll(user1.id, tag = "c"))
.hasSize(2)
assertThat(noteRepo.findAll(user2.id, tag = "c"))
.hasSize(1)
}
}
@Nested
@DisplayName("find() | exists()")
inner class FindExists {
@Test
@Suppress("UNCHECKED_CAST")
fun `find an existing note`() {
val fakeNote = noteRepo.insertFakeNote(user1)
val converter = beanContext.getBean(NoteConverter::class.java)
val note = db.notes.find { it.title eq fakeNote.meta.title }!!
.let { entity ->
val tags = db.tags.filter { it.noteUuid eq entity.uuid }.mapColumns { it.name } as List<String>
converter.toPersistedNote(entity, 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 = noteRepo.insertFakeNote(user1)
assertThat(noteRepo.find(user2.id, note.uuid)).isNull()
assertThat(noteRepo.exists(user2.id, note.uuid)).isFalse
}
@Test
fun `find a non existing note`() {
noteRepo.insertFakeNote(user1)
val uuid = fakeUuid()
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 = noteRepo.insertFakeNote(user1)
assertThat(noteRepo.delete(user1.id, note.uuid)).isTrue
}
@Test
fun `delete an existing note for the wrong user`() {
val note = noteRepo.insertFakeNote(user1)
assertThat(noteRepo.delete(1000, note.uuid)).isFalse
}
}
@Nested
@DisplayName("getTags()")
inner class Tags {
@Test
fun getTags() {
val notes1 = noteRepo.insertFakeNotes(user1, count = 3)
val notes2 = noteRepo.insertFakeNotes(user2, count = 1)
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 = noteRepo.insertFakeNote(user1)
val newNote1 = fakeNote()
assertThat(noteRepo.update(user1.id, note1.uuid, newNote1)).isNotNull
assertThat(noteRepo.find(user1.id, note1.uuid))
.isEqualToComparingOnlyGivenFields(newNote1, "meta", "markdown", "html")
val note2 = noteRepo.insertFakeNote(user1)
val newNote2 = fakeNote().let {
it.copy(meta = it.meta.copy(tags = tagGenerator().take(3).toList()))
}
assertThat(noteRepo.update(user1.id, note2.uuid, newNote2))
.isNotNull
assertThat(noteRepo.find(user1.id, note2.uuid))
.isEqualToComparingOnlyGivenFields(newNote2, "meta", "markdown", "html")
}
}
@Nested
inner class Trash {
@Test
fun `trashed noted should be restored`() {
val note1 = noteRepo.insertFakeNote(user1, "1", listOf("a", "b"))
assertThat(noteRepo.delete(user1.id, note1.uuid, permanent = false))
.isTrue
val isDeleted = db.notes
.find { it.uuid eq note1.uuid }
?.deleted
assertThat(isDeleted).`as`("Check that Notes.deleted is true").isTrue
assertThat(noteRepo.restore(user1.id, note1.uuid)).isTrue
val isDeleted2 = db.notes
.find { it.uuid eq note1.uuid }
?.deleted
assertThat(isDeleted2).`as`("Check that Notes.deleted is false after restore()").isFalse
}
@Test
fun `permanent delete`() {
val note = noteRepo.insertFakeNote(user1)
assertThat(noteRepo.delete(user1.id, note.uuid, permanent = true)).isTrue
assertThat(noteRepo.restore(user1.id, note.uuid)).isFalse
}
}
@Test
fun count() {
assertThat(noteRepo.count(user1.id)).isEqualTo(0)
noteRepo.insertFakeNotes(user1, count = 10)
assertThat(noteRepo.count(user1.id)).isEqualTo(10)
}
@Test
fun countWithTag() {
noteRepo.insertFakeNote(user1, tags = listOf("a", "b"))
noteRepo.insertFakeNote(user1, tags = emptyList())
noteRepo.insertFakeNote(user1, tags = listOf("a"))
noteRepo.insertFakeNote(user1, tags = emptyList())
assertThat(noteRepo.count(user1.id, tag = "a")).isEqualTo(2)
}
@Test
fun export() {
val notes = noteRepo.insertFakeNotes(user1, count = 4)
noteRepo.delete(user1.id, notes.first().uuid, permanent = false)
val export = noteRepo.export(user1.id)
val expected = notes.mapIndexed { i, n ->
ExportedNote(
title = n.meta.title,
tags = n.meta.tags,
markdown = n.markdown,
html = n.html,
updatedAt = n.updatedAt,
trash = i == 0,
)
}
assertThat(export)
.usingElementComparatorIgnoringFields("updatedAt")
.containsExactlyInAnyOrderElementsOf(expected)
}
@Test
fun findAllDetails() {
val notes = noteRepo.insertFakeNotes(user1, count = 10)
val res = noteRepo.findAllDetails(user1.id)
assertThat(res)
.usingElementComparatorIgnoringFields("updatedAt")
.containsExactlyInAnyOrderElementsOf(notes)
}
@Test
fun access() {
val n = noteRepo.insertFakeNote(user1)
noteRepo.makePublic(user1.id, n.uuid)
assertThat(noteRepo.findPublic(n.uuid))
.isEqualToIgnoringGivenFields(n.copy(public = true), "updatedAt")
noteRepo.makePrivate(user1.id, n.uuid)
assertThat(noteRepo.findPublic(n.uuid)).isNull()
}
}
@@ -0,0 +1,29 @@
package be.simplenotes.persistance.notes
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.KMariadbContainer
import be.simplenotes.persistance.h2dataSourceConfig
import be.simplenotes.persistance.mariadbDataSourceConfig
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.parallel.ResourceLock
@ResourceLock("h2")
internal class H2NoteRepositoryImplTests : BaseNoteRepositoryImplTest() {
override fun dataSourceConfig() = h2dataSourceConfig()
}
@ResourceLock("mariadb")
internal class MariaDbNoteRepositoryImplTests : BaseNoteRepositoryImplTest() {
lateinit var mariaDB: KMariadbContainer
@AfterAll
fun stopMariaDB() {
mariaDB.stop()
}
override fun dataSourceConfig(): DataSourceConfig {
mariaDB = KMariadbContainer()
mariaDB.start()
return mariadbDataSourceConfig(mariaDB.jdbcUrl)
}
}
@@ -0,0 +1,82 @@
package be.simplenotes.persistance.users
import be.simplenotes.persistance.DbTest
import be.simplenotes.persistance.repositories.UserRepository
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.eq
import me.liuwj.ktorm.entity.find
import me.liuwj.ktorm.entity.toList
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
internal abstract class BaseUserRepositoryImplTest : DbTest() {
private lateinit var userRepo: UserRepository
private lateinit var db: Database
@BeforeEach
fun setup() {
userRepo = beanContext.getBean()
db = beanContext.getBean()
}
@Test
fun `insert user`() {
val user = fakeUser()
assertThat(userRepo.create(user))
.isNotNull
.extracting("username", "password")
.contains(user.username, user.password)
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 = fakeUser()
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 = fakeUser()
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
}
}
}
@@ -0,0 +1,29 @@
package be.simplenotes.persistance.users
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.KMariadbContainer
import be.simplenotes.persistance.h2dataSourceConfig
import be.simplenotes.persistance.mariadbDataSourceConfig
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.parallel.ResourceLock
@ResourceLock("h2")
internal class UserRepositoryImplTest : BaseUserRepositoryImplTest() {
override fun dataSourceConfig() = h2dataSourceConfig()
}
@ResourceLock("mariadb")
internal class MariaDbUserRepositoryImplTest : BaseUserRepositoryImplTest() {
lateinit var mariaDB: KMariadbContainer
@AfterAll
fun stopMariaDB() {
mariaDB.stop()
}
override fun dataSourceConfig(): DataSourceConfig {
mariaDB = KMariadbContainer()
mariaDB.start()
return mariadbDataSourceConfig(mariaDB.jdbcUrl)
}
}
+35
View File
@@ -0,0 +1,35 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import io.micronaut.context.BeanContext
import org.flywaydb.core.Flyway
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import javax.sql.DataSource
abstract class DbTest {
abstract fun dataSourceConfig(): DataSourceConfig
val beanContext = BeanContext.build()
inline fun <reified T> BeanContext.getBean(): T = getBean(T::class.java)
@BeforeAll
fun setComponent() {
beanContext.registerSingleton(dataSourceConfig())
}
@BeforeEach
fun beforeEach() {
val migration = beanContext.getBean<DbMigrations>()
val dataSource = beanContext.getBean<DataSource>()
Flyway.configure()
.dataSource(dataSource)
.load()
.clean()
migration.migrate()
}
}
@@ -0,0 +1,37 @@
package be.simplenotes.persistance.notes
import be.simplenotes.persistance.repositories.NoteRepository
import be.simplenotes.types.*
import com.github.javafaker.Faker
import java.util.*
private val faker = Faker()
fun fakeUuid() = UUID.randomUUID()!!
fun fakeTitle() = faker.lorem().characters(3, 50)!!
fun tagGenerator() = generateSequence { faker.lorem().word() }
fun fakeTags() = tagGenerator().take(faker.random().nextInt(0, 3)).toList()
fun fakeContent() = faker.lorem().paragraph(faker.random().nextInt(0, 3))!!
fun fakeNote() = Note(NoteMetadata(fakeTitle(), fakeTags()), fakeContent(), fakeContent())
fun PersistedNote.toPersistedMeta() =
PersistedNoteMetadata(meta.title, meta.tags, updatedAt, uuid)
fun NoteRepository.insertFakeNote(
userId: Int,
title: String,
tags: List<String> = emptyList(),
md: String = "md",
html: String = "html",
): PersistedNote = create(userId, Note(NoteMetadata(title, tags), md, html))
fun NoteRepository.insertFakeNote(
user: PersistedUser,
title: String = fakeTitle(),
tags: List<String> = fakeTags(),
md: String = fakeContent(),
html: String = fakeContent(),
): PersistedNote = insertFakeNote(user.id, title, tags, md, html)
fun NoteRepository.insertFakeNotes(user: PersistedUser, count: Int): List<PersistedNote> =
generateSequence { insertFakeNote(user) }.take(count).toList()
@@ -0,0 +1,14 @@
package be.simplenotes.persistance.users
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User
import com.github.javafaker.Faker
private val faker = Faker()
fun fakeUser() = User(faker.name().username(), faker.internet().password())
fun UserRepository.createFakeUser(): PersistedUser? {
return create(fakeUser())
}
+15
View File
@@ -0,0 +1,15 @@
<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="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
<logger name="me.liuwj.ktorm.database" level="DEBUG"/>
<logger name="com.zaxxer.hikari" level="INFO"/>
<logger name="org.flywaydb.core" level="DEBUG"/>
</configuration>