Skip to content

Commit

Permalink
migrate to springboot example + db commands json data type support
Browse files Browse the repository at this point in the history
  • Loading branch information
toronik committed Feb 21, 2024
1 parent 9848a52 commit 0067818
Show file tree
Hide file tree
Showing 31 changed files with 411 additions and 478 deletions.
26 changes: 9 additions & 17 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
ext.kotlin_version = '1.9.10'
ext.ktor_version = '2.3.7'
ext.exposed_version = '0.41.1'
ext.kotlin_version = '1.9.22'
ext.klogging_version = '3.0.4'
ext.jacksonKt_version = '2.14.1'
ext.libVersion = '2024.0.3'
ext.libVersion = '2024.0.4'
repositories {
mavenCentral()
maven {
Expand All @@ -13,7 +12,7 @@ buildscript {
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.1"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.5"
classpath "org.jlleitschuh.gradle:ktlint-gradle:11.6.1"
}
}
Expand Down Expand Up @@ -52,22 +51,15 @@ subprojects {
group = 'io.github.adven27'
version = libVersion

sourceCompatibility = 11
targetCompatibility = 11
sourceCompatibility = 17
targetCompatibility = 17
compileJava.options.encoding = 'utf-8'
compileTestJava.options.encoding = 'utf-8'

compileKotlin {
tasks.withType(KotlinCompile) {
kotlinOptions {
freeCompilerArgs = ['-Xjsr305=strict']
jvmTarget = '11'
}
}

compileTestKotlin {
kotlinOptions {
freeCompilerArgs = ['-Xjsr305=strict']
jvmTarget = '11'
freeCompilerArgs += '-Xjsr305=strict'
jvmTarget = '17'
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ open class Content(val body: String, val type: String) {
class Text(content: String) : Content(content, "text")

fun pretty() = body.pretty(type)

override fun toString() = pretty()
}

interface ContentPrinter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,20 @@ class ExamExtension(private vararg var plugins: ExamPlugin) : ConcordionExtensio
}.first
}

const val VERIFIER_JSON = "json"
const val VERIFIER_JSON_IGNORE_EXTRA_FIELDS = "jsonIgnoreExtraFields"
const val VERIFIER_JSON_ARRAY_ORDERED = "jsonArrayOrdered"
const val VERIFIER_XML = "xml"
const val VERIFIER_TEXT = "text"

val CONTENT_VERIFIERS: MutableMap<String, ContentVerifier> = mutableMapOf(
"json" to JsonVerifier(),
"jsonIgnoreExtraFields" to JsonVerifier { it.withOptions(IGNORING_EXTRA_FIELDS) },
"jsonArrayOrdered" to JsonVerifier { it.withOptions(it.options.apply { remove(IGNORING_ARRAY_ORDER) }) },
"xml" to XmlVerifier(),
"text" to ContentVerifier.Default("text")
VERIFIER_JSON to JsonVerifier(),
VERIFIER_JSON_IGNORE_EXTRA_FIELDS to JsonVerifier { it.withOptions(IGNORING_EXTRA_FIELDS) },
VERIFIER_JSON_ARRAY_ORDERED to JsonVerifier {
it.withOptions(it.options.apply { remove(IGNORING_ARRAY_ORDER) })
},
VERIFIER_XML to XmlVerifier(),
VERIFIER_TEXT to ContentVerifier.Default("text")
)

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package io.github.adven27.concordion.extensions.exam.core.handlebars.matchers

import com.github.jknack.handlebars.Context
import com.github.jknack.handlebars.Options
import io.github.adven27.concordion.extensions.exam.core.handlebars.ExamHelper
import io.github.adven27.concordion.extensions.exam.core.utils.DateFormattedAndWithin.Companion.PARAMS_SEPARATOR
import io.github.adven27.concordion.extensions.exam.core.utils.parseDate
import io.github.adven27.concordion.extensions.exam.core.utils.toLocalDate
import io.github.adven27.concordion.extensions.exam.core.utils.toLocalDateTime
import org.concordion.api.Evaluator
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.regex.Pattern

const val PLACEHOLDER_TYPE = "placeholder_type"
const val DB_ACTUAL = "db_actual"
const val ISO_LOCAL_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"
const val ISO_LOCAL_DATE_FORMAT = "yyyy-MM-dd"

Expand Down Expand Up @@ -80,11 +76,7 @@ enum class MatcherHelpers(
expected = "\${json-unit.regex}\\d+"
) {
override fun invoke(context: Any?, options: Options): Any =
if (placeholderType(options.context) == "db") {
regexMatches(context.toString(), dbActual(options.context))
} else {
"\${${placeholderType(options.context)}-unit.regex}$context"
}
"\${${placeholderType(options.context)}-unit.regex}$context"
},
after(
example = "{{after (today)}}",
Expand Down Expand Up @@ -175,8 +167,6 @@ enum class MatcherHelpers(
"\${${placeholderType(options.context)}-unit.matches:$context}${options.param(0, "")}"
};

protected fun dbActual(context: Context) = (context.model() as Evaluator).getVariable("#$DB_ACTUAL")

override fun apply(context: Any?, options: Options): Any? {
validate(options)
val result = try {
Expand All @@ -190,6 +180,3 @@ enum class MatcherHelpers(
override fun toString() = this.describe()
abstract operator fun invoke(context: Any?, options: Options): Any?
}

private fun regexMatches(p: String, value: Any?): Boolean =
value.takeIf { it != null }?.let { Pattern.compile(p).matcher(it.toString()).matches() } ?: false
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.github.adven27.concordion.extensions.exam.db

import com.github.jknack.handlebars.Options
import io.github.adven27.concordion.extensions.exam.core.Content
import io.github.adven27.concordion.extensions.exam.core.ExamPlugin
import io.github.adven27.concordion.extensions.exam.core.html.pre
import io.github.adven27.concordion.extensions.exam.core.html.span
import io.github.adven27.concordion.extensions.exam.db.commands.DbCleanCommand
import io.github.adven27.concordion.extensions.exam.db.commands.DbExecuteCommand
Expand Down Expand Up @@ -107,19 +109,21 @@ class DbPlugin @JvmOverloads constructor(
interface ValuePrinter {
open class Default @JvmOverloads constructor(
formatter: DateTimeFormatter = ISO_LOCAL_DATE_TIME,
private val tableColumnType: Map<TableColumn, String> = mapOf()
private val tableColumnStyle: Map<TableColumn, String> = mapOf()
) : AbstractDefault(formatter) {
data class TableColumn(val table: String, val column: String)

override fun orElse(value: Any): String = value.toString()

override fun wrap(table: String, column: String, value: Any?): Element =
tableColumnType.entries
.filter { (tc, _) -> tc.eq(table, column) }
.map { it.value }
.firstOrNull()
?.let { Element("pre").addStyleClass(it).appendText(print(table, column, value)) }
?: super.wrap(table, column, value)
super.wrap(table, column, value).let { e ->
tableColumnStyle.entries
.filter { (tc, _) -> tc.eq(table, column) }
.map { it.value }
.firstOrNull()
?.let { e.addStyleClass(it) }
?: e
}

private fun TableColumn.eq(t: String, c: String) =
table.equals(t, ignoreCase = true) && column.equals(c, ignoreCase = true)
Expand All @@ -131,12 +135,16 @@ class DbPlugin @JvmOverloads constructor(
is Array<*> -> value.contentToString()
is java.sql.Date -> printDate(Date(value.time))
is Date -> printDate(value)
is Content -> value.pretty()
else -> orElse(value)
}

private fun printDate(value: Date) = formatter.withZone(ZoneId.systemDefault()).format(value.toInstant())
override fun wrap(table: String, column: String, value: Any?): Element =
span(print(table, column, value)).el
when (value) {
is Content -> pre(print(table, column, value), "class" to value.type).el
else -> span(print(table, column, value)).el
}

abstract fun orElse(value: Any): String
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.adven27.concordion.extensions.exam.db

import io.github.adven27.concordion.extensions.exam.core.Content
import io.github.adven27.concordion.extensions.exam.core.commands.AwaitConfig
import io.github.adven27.concordion.extensions.exam.core.commands.Verifier
import io.github.adven27.concordion.extensions.exam.core.html.fileExt
Expand All @@ -8,6 +9,7 @@ import io.github.adven27.concordion.extensions.exam.db.builder.CompareOperation.
import io.github.adven27.concordion.extensions.exam.db.builder.ContainsFilterTable
import io.github.adven27.concordion.extensions.exam.db.builder.DataBaseSeedingException
import io.github.adven27.concordion.extensions.exam.db.builder.ExamDataSet
import io.github.adven27.concordion.extensions.exam.db.builder.ExamTable
import io.github.adven27.concordion.extensions.exam.db.builder.JSONDataSet
import io.github.adven27.concordion.extensions.exam.db.builder.SeedStrategy
import io.github.adven27.concordion.extensions.exam.db.builder.SeedStrategy.CLEAN_INSERT
Expand Down Expand Up @@ -80,6 +82,19 @@ open class DbTester @JvmOverloads constructor(
strategy.operation.execute(connection(ds), ExamDataSet(table, eval))
}

fun metaData(seed: TableSeed) = TableSeed(
seed.ds,
ExamTable(
CompositeTable(
connection(seed.ds)
.createTable(seed.table.tableName()).withColumnsAsIn(seed.table).tableMetaData,
(seed.table as ExamTable).delegate
),
seed.table.eval
),
seed.strategy
)

fun seed(seed: FilesSeed, eval: Evaluator) = requireAllowedStrategy(seed).let {
try {
order(loadDataSet(eval, it.datasets), it.tableOrdering).apply {
Expand Down Expand Up @@ -488,7 +503,7 @@ open class DbTesterBase @JvmOverloads constructor(
createQueryTable(tableName, "SELECT * FROM $tableName WHERE $filter")

fun actualWithDependentTables(ds: String?, table: String): IDataSet = connection(ds).let {
it.createDataSet(getAllDependentTables(it, QualifiedTableName(table.uppercase(), it.schema).qualifiedName))
it.createDataSet(getAllDependentTables(it, QualifiedTableName(table, it.schema).qualifiedName))
}

protected fun actualDataSet(tables: Array<String>): IDataSet = connection.createDataSet(tables)
Expand Down Expand Up @@ -570,13 +585,18 @@ private fun List<Difference>.prettyPrint(): String =
.toSortedMap().entries.joinToString("\n") { """${it.key}: ${it.value}""" }

class JsonbPostgresqlDataTypeFactory : PostgresqlDataTypeFactory() {
override fun createDataType(sqlType: Int, sqlTypeName: String?): DataType =
if (sqlTypeName == "jsonb") JsonbDataType() else super.createDataType(sqlType, sqlTypeName)
override fun createDataType(sqlType: Int, sqlTypeName: String?): DataType = when (sqlTypeName) {
in listOf("jsonb", "json") -> JsonbDataType(sqlTypeName!!)
else -> super.createDataType(sqlType, sqlTypeName)
}

class JsonbDataType : AbstractDataType("jsonb", Types.OTHER, String::class.java, false) {
override fun typeCast(obj: Any?): Any = obj.toString()
class JsonbDataType(name: String) : AbstractDataType(name, Types.OTHER, Content.Json::class.java, false) {
override fun typeCast(obj: Any?): Content.Json? = obj?.let {
if (it is Content.Json) it else Content.Json(it.toString())
}

override fun getSqlValue(column: Int, resultSet: ResultSet): Any? = resultSet.getString(column)
override fun getSqlValue(column: Int, resultSet: ResultSet): Any? =
resultSet.getString(column)?.let { Content.Json(it) }

override fun setSqlValue(value: Any?, column: Int, statement: PreparedStatement) = statement.setObject(
column,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,18 @@ class ExamDataSetIterator(private val delegate: ITableIterator, private val eval
override fun getTable(): ITable = ExamTable(delegate.table, eval)
}

class ExamTable(private val delegate: ITable, private val eval: Evaluator) : ITable {
class ExamTable(val delegate: ITable, val eval: Evaluator) : ITable {
override fun getTableMetaData(): ITableMetaData = delegate.tableMetaData
override fun getRowCount(): Int = delegate.rowCount

@Throws(DataSetException::class)
override fun getValue(row: Int, column: String): Any? {
val value = delegate.getValue(row, column)
val dataType = tableMetaData.columns.first { it.columnName.equals(column, ignoreCase = true) }.dataType
return try {
when {
value is String && value.isRange() -> value.toRange().toList().let { it[row % it.size] }
value is String && !value.startsWith("!{") -> eval.resolveToObj(value)
value is String -> dataType.typeCast(eval.resolveToObj(value))
else -> value
}
} catch (expected: Throwable) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.adven27.concordion.extensions.exam.db.commands

import io.github.adven27.concordion.extensions.exam.core.Content
import io.github.adven27.concordion.extensions.exam.core.ContentVerifier
import io.github.adven27.concordion.extensions.exam.core.ContentVerifier.Companion.setActualIfNeeded
import io.github.adven27.concordion.extensions.exam.core.ExamExtension.Companion.contentVerifier
Expand All @@ -16,6 +17,7 @@ import java.util.*

open class ExamMatchersAwareValueComparer : IsActualEqualToExpectedValueComparer() {
protected lateinit var evaluator: Evaluator
protected var error = ""

fun setEvaluator(evaluator: Evaluator): ExamMatchersAwareValueComparer {
this.evaluator = evaluator
Expand All @@ -30,15 +32,29 @@ open class ExamMatchersAwareValueComparer : IsActualEqualToExpectedValueComparer
dataType: DataType,
expected: Any?,
actual: Any?
): Boolean = when {
expected.isError() -> false
expected.isMatcher() -> setActualIfNeeded(expected as String, actual, evaluator).let {
ContentVerifier.matcher(it, "\${test-unit.").matches(actual)
}
): Boolean {
error = ""
return when {
expected.isError() -> false
expected.isMatcher() -> setActualIfNeeded(expected as String, actual, evaluator).let {
ContentVerifier.matcher(it, "\${test-unit.").matches(actual)
}

actual is Content && expected is Content -> verify(actual, expected.body)
actual is Content && expected is String -> verify(actual, expected)

else -> super.isExpected(expectedTable, actualTable, rowNum, columnName, dataType, expected, actual)
else -> super.isExpected(expectedTable, actualTable, rowNum, columnName, dataType, expected, actual)
}
}

private fun verify(actual: Content, expected: String) =
contentVerifier(actual.type).verify(expected, actual.body, evaluator)
.onFailure { error = " because:\n" + it.rootCauseMessage() }
.isSuccess

override fun makeFailMessage(expectedValue: Any?, actualValue: Any?): String =
super.makeFailMessage(expectedValue, actualValue) + error

companion object {
@JvmField
var ERROR_MARKER = "ERROR RETRIEVING VALUE: "
Expand Down Expand Up @@ -84,8 +100,7 @@ fun sortedTable(table: ITable, columns: Array<String>, rowComparator: RowCompara
setRowComparator(rowComparator.init(table, columns))
}

open class TypedColumnComparer(val type: String) : ExamMatchersAwareValueComparer() {
private var error = ""
open class VerifierColumnComparer(private val verifier: String) : ExamMatchersAwareValueComparer() {
override fun isExpected(
expectedTable: ITable?,
actualTable: ITable?,
Expand All @@ -94,14 +109,11 @@ open class TypedColumnComparer(val type: String) : ExamMatchersAwareValueCompare
dataType: DataType,
expected: Any?,
actual: Any?
) = contentVerifier(type).verify(expected.toString(), actual.toString(), evaluator)
) = contentVerifier(verifier).verify(expected.toString(), actual.toString(), evaluator)
.onFailure { error = " because:\n" + it.rootCauseMessage() }
.onSuccess { error = "" }
.isSuccess

override fun makeFailMessage(expectedValue: Any?, actualValue: Any?): String =
super.makeFailMessage(expectedValue, actualValue) + error
}

@Suppress("unused")
class JsonColumnComparer : TypedColumnComparer("json")
class JsonColumnComparer : VerifierColumnComparer("json")
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ open class DbSetCommand(
override fun render(commandCall: CommandCall, result: Model) = renderer.render(commandCall, result)
override fun process(model: Model, eval: Evaluator, recorder: ResultRecorder) = model.apply {
dbTester.seed(model.seed, eval)
}
}.copy(seed = dbTester.metaData(model.seed))

data class Model(val seed: TableSeed, val caption: String?)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.adven27.concordion.extensions.exam.db.commands.check

import io.github.adven27.concordion.extensions.exam.core.Content
import io.github.adven27.concordion.extensions.exam.core.html.Html
import io.github.adven27.concordion.extensions.exam.core.html.div
import io.github.adven27.concordion.extensions.exam.core.html.errorMessage
Expand Down Expand Up @@ -97,10 +98,14 @@ open class BaseResultRenderer(private val printer: ValuePrinter) : DbCheckComman
?: td.success(expected.tableName(), col, expected[row, col], actual[row, col])
}

private fun Html.diff(d: Difference) = this(
Html("del", printer.print(d.expectedTable.tableName(), d.columnName, d.expectedValue), "class" to "me-1"),
Html("ins", printer.print(d.actualTable.tableName(), d.columnName, d.actualValue))
).css("table-danger").tooltip(d.failMessage)
private fun Html.diff(d: Difference): Html {
val act = d.actualValue
val exp = if (act is Content) Content(body = d.expectedValue.toString(), type = act.type) else d.expectedValue
return this(
Html("del", printer.print(d.actualTable.tableName(), d.columnName, exp)).css("expected"),
Html("ins", printer.print(d.actualTable.tableName(), d.columnName, act)).css("actual")
).css("${if (act is Content) act.type else ""} failure").tooltip(d.failMessage)
}

private operator fun List<Difference>.get(row: Int, col: String) =
singleOrNull { it.rowIndex == row && it.columnName.equals(col, ignoreCase = true) }
Expand Down
Loading

0 comments on commit 0067818

Please sign in to comment.