Update PostgresBackend with configurable connections and test implementations
The PostgresBackend class has been updated to allow for more configurable database connection methods. This includes more granular control over specific connection properties and the introduction of a sealed class constructor for creating distinct JDBC connection URLs. Also, test implementations for credential manager and Postgres backend have been updated, particularly to handle coroutine suspension. Furthermore, dependencies for test modules and kotlinx coroutines have been added to the build scripts.main
parent
8a9732eff9
commit
fd47bc3baa
|
|
@ -26,9 +26,9 @@ testing {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(platform("org.junit:junit-bom:5.10.2"))
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|
||||||
testImplementation("com.willowtreeapps.assertk:assertk:0.28.1")
|
testImplementation("com.willowtreeapps.assertk:assertk:0.28.1")
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
|
testImplementation(platform("org.junit:junit-bom:5.10.2"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ kotlin-logging = {module = "io.github.oshai:kotlin-logging-jvm", version.ref = "
|
||||||
kotlinx-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.5.0"
|
kotlinx-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.5.0"
|
||||||
kotlinx-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
|
kotlinx-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
|
||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" }
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" }
|
||||||
|
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx" }
|
||||||
ktor-server-content-negotation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
|
ktor-server-content-negotation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
|
||||||
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor"}
|
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor"}
|
||||||
|
|
||||||
|
|
@ -81,6 +82,10 @@ kotlinx = [
|
||||||
"kotlinx-json",
|
"kotlinx-json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
kotlinx-test = [
|
||||||
|
"kotlinx-coroutines-test"
|
||||||
|
]
|
||||||
|
|
||||||
logging = [
|
logging = [
|
||||||
"slf4j-api",
|
"slf4j-api",
|
||||||
"logback-classic",
|
"logback-classic",
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,6 @@ dependencies {
|
||||||
implementation(project(":modules:ModuleCore"))
|
implementation(project(":modules:ModuleCore"))
|
||||||
implementation("org.postgresql:postgresql:42.7.3")
|
implementation("org.postgresql:postgresql:42.7.3")
|
||||||
implementation(libs.bundles.exposed)
|
implementation(libs.bundles.exposed)
|
||||||
|
implementation(libs.bundles.kotlinx.test)
|
||||||
|
testImplementation("com.appmattus.fixture:fixture:1.2.0")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
package de.itkl.modCredentialManager
|
package de.itkl.modCredentialManager
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface CredentialManager {
|
interface CredentialManager {
|
||||||
fun find(id: String): Credential?
|
suspend fun search(searchTerm: String): Flow<Credential>
|
||||||
|
|
||||||
fun add(credential: Credential)
|
suspend fun find(id: String): Credential?
|
||||||
|
|
||||||
fun delete(id: String)
|
suspend fun add(credential: Credential)
|
||||||
|
|
||||||
|
suspend fun delete(id: String)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
package de.itkl.modCredentialManager.postgresBackend
|
package de.itkl.modCredentialManager.postgresBackend
|
||||||
|
|
||||||
import de.itkl.modCredentialManager.Credential
|
import de.itkl.modCredentialManager.*
|
||||||
import de.itkl.modCredentialManager.CredentialManager
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import org.jetbrains.exposed.dao.UUIDEntity
|
import org.jetbrains.exposed.dao.UUIDEntity
|
||||||
|
|
@ -12,40 +16,139 @@ import org.jetbrains.exposed.dao.id.UUIDTable
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import java.sql.Connection
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import kotlin.sequences.Sequence
|
||||||
import kotlin.time.Duration.Companion.hours
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
|
||||||
private val Log = KotlinLogging.logger { }
|
private val Log = KotlinLogging.logger { }
|
||||||
|
|
||||||
class PostgresBackend : CredentialManager {
|
class PostgresBackend(config: Config) : CredentialManager {
|
||||||
val dbConnection = Database.connect(
|
data class Config(
|
||||||
"jdbc:postgresql://localhost:5432/postgres",
|
val user: String,
|
||||||
driver = "org.postgresql.Driver",
|
val url: JdbcUrl,
|
||||||
user = "kinch",
|
) {
|
||||||
|
fun toConnection(): Database {
|
||||||
|
return Database.connect(
|
||||||
|
url = url.toString(),
|
||||||
|
driver = url.driver,
|
||||||
|
user = user,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface JdbcUrl {
|
||||||
|
val driver: String
|
||||||
|
|
||||||
|
data class PostgreSql(
|
||||||
|
val host: String,
|
||||||
|
val port: Int,
|
||||||
|
val databaseName: String,
|
||||||
|
val batchInsert: Boolean = false,
|
||||||
|
) : JdbcUrl {
|
||||||
|
override val driver: String
|
||||||
|
get() = "org.postgresql.Driver"
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "jdbc:postgresql://$host:$port/$databaseName?reWriteBatchedInserts=$batchInsert"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dbConnection = config.toConnection()
|
||||||
|
|
||||||
|
private suspend inline fun <T> transaction(
|
||||||
|
writable: Boolean = false,
|
||||||
|
noinline op: Transaction.() -> T,
|
||||||
|
): T =
|
||||||
|
coroutineScope {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
transaction(
|
||||||
|
transactionIsolation = Connection.TRANSACTION_READ_UNCOMMITTED,
|
||||||
|
readOnly = !writable,
|
||||||
|
db = dbConnection,
|
||||||
|
op,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
runBlocking {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(StdOutSqlLogger)
|
addLogger(StdOutSqlLogger)
|
||||||
SchemaUtils.create(Credentials)
|
SchemaUtils.create(Credentials)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun find(id: String): Credential? {
|
override suspend fun search(searchTerm: String): Flow<Credential> {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun add(credential: Credential) {
|
override suspend fun find(id: String): Credential? {
|
||||||
transaction {
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun listInsert(credentials: Sequence<Credential>) {
|
||||||
|
transaction(writable = true) {
|
||||||
|
credentials.forEach {
|
||||||
|
insert(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun batchInsert(credentials: Sequence<Credential>) {
|
||||||
|
transaction(writable = true) {
|
||||||
|
Credentials.batchInsert(
|
||||||
|
credentials,
|
||||||
|
ignore = true,
|
||||||
|
shouldReturnGeneratedValues = false,
|
||||||
|
) { cred ->
|
||||||
|
when (cred) {
|
||||||
|
is BearerToken -> {
|
||||||
|
this[Credentials.displayName] = cred.id
|
||||||
|
// fill other fields here ...
|
||||||
|
}
|
||||||
|
is RefreshToken -> {
|
||||||
|
this[Credentials.displayName] = cred.id
|
||||||
|
// fill other fields here ...
|
||||||
|
}
|
||||||
|
is UsernameAndPassword -> {
|
||||||
|
this[Credentials.displayName] = cred.id
|
||||||
|
this[Credentials.username] = cred.username
|
||||||
|
this[Credentials.secret] = cred.password
|
||||||
|
this[Credentials.notes] = cred.notes
|
||||||
|
this[Credentials.expiresAt] = Clock.System.now() + 1.hours
|
||||||
|
}
|
||||||
|
// handle other credential types here ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun add(credential: Credential) {
|
||||||
|
transaction(writable = true) {
|
||||||
|
insert(credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insert(credential: Credential) {
|
||||||
|
when (credential) {
|
||||||
|
is BearerToken -> TODO()
|
||||||
|
is RefreshToken -> TODO()
|
||||||
|
is UsernameAndPassword -> {
|
||||||
DbCredential.new {
|
DbCredential.new {
|
||||||
displayName = credential.id
|
displayName = credential.id
|
||||||
secret = "timo"
|
secret = credential.password
|
||||||
|
username = credential.username
|
||||||
|
notes = credential.notes
|
||||||
expiresAt = Clock.System.now() + 1.hours
|
expiresAt = Clock.System.now() + 1.hours
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun delete(id: String) {
|
override suspend fun delete(id: String) {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,73 @@
|
||||||
package de.itkl.modCredentialManager.postgresBackend
|
package de.itkl.modCredentialManager.postgresBackend
|
||||||
|
|
||||||
|
import com.appmattus.kotlinfixture.kotlinFixture
|
||||||
import de.itkl.modCredentialManager.UsernameAndPassword
|
import de.itkl.modCredentialManager.UsernameAndPassword
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.Assertions.*
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.MethodOrderer
|
||||||
|
import org.junit.jupiter.api.Order
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder
|
||||||
|
|
||||||
|
@TestMethodOrder(
|
||||||
|
MethodOrderer.OrderAnnotation::class,
|
||||||
|
)
|
||||||
class PostgresBackendTest {
|
class PostgresBackendTest {
|
||||||
|
val fixture = kotlinFixture()
|
||||||
|
private val db = PostgresBackend(
|
||||||
|
PostgresBackend.Config(
|
||||||
|
user = "tbr",
|
||||||
|
url = PostgresBackend.JdbcUrl.PostgreSql(
|
||||||
|
host = "localhost",
|
||||||
|
port = 5432,
|
||||||
|
databaseName = "postgres",
|
||||||
|
batchInsert = true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun randomCredentials(): Sequence<UsernameAndPassword> {
|
||||||
|
return (1..10_000)
|
||||||
|
.asSequence()
|
||||||
|
.map {
|
||||||
|
fixture<UsernameAndPassword>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Order(1)
|
||||||
@Test
|
@Test
|
||||||
fun `can create a table`() {
|
fun `dumb insert`() =
|
||||||
PostgresBackend().add(UsernameAndPassword("username", "password", username = "", password = ""))
|
runTest {
|
||||||
|
randomCredentials().forEach { db.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Disabled
|
||||||
|
// @Order(2)
|
||||||
|
// @Test
|
||||||
|
// fun `async insert`() =
|
||||||
|
// runTest {
|
||||||
|
// val gate = Semaphore(10)
|
||||||
|
// randomCredentials().map {
|
||||||
|
// async {
|
||||||
|
// gate.withPermit {
|
||||||
|
// db.add(it)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .awaitAll()
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Order(3)
|
||||||
|
@Test
|
||||||
|
fun `single transaction`() =
|
||||||
|
runTest {
|
||||||
|
db.listInsert(randomCredentials())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Order(4)
|
||||||
|
@Test
|
||||||
|
fun `batch insert`() =
|
||||||
|
runTest {
|
||||||
|
db.batchInsert(randomCredentials())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
Loading…
Reference in New Issue