Skip to content

Commit

Permalink
State store (#453)
Browse files Browse the repository at this point in the history
StateStore implementation can be provided outside by the users, thus you can store the state in any other provider such as Gitlab, another file storage or database #370
  • Loading branch information
osoykan authored May 28, 2024
1 parent a9cef0d commit dbc2089
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.kotest.matchers.shouldBe
import stove.ktor.example.application.*
import stove.ktor.example.domain.*
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds

class ExampleTest : FunSpec({
data class ProductOfTest(
Expand Down Expand Up @@ -56,10 +57,10 @@ class ExampleTest : FunSpec({
}

kafka {
shouldBePublished<DomainEvents.ProductUpdated> {
shouldBePublished<DomainEvents.ProductUpdated>(20.seconds) {
actual.id == givenId && actual.name == givenName
}
shouldBeConsumed<DomainEvents.ProductUpdated> {
shouldBeConsumed<DomainEvents.ProductUpdated>(20.seconds) {
actual.id == givenId && actual.name == givenName
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,84 @@ import com.trendol.stove.testing.e2e.rdbms.postgres.*
import com.trendyol.stove.testing.e2e.*
import com.trendyol.stove.testing.e2e.http.httpClient
import com.trendyol.stove.testing.e2e.standalone.kafka.*
import com.trendyol.stove.testing.e2e.system.TestSystem
import com.trendyol.stove.testing.e2e.system.*
import com.trendyol.stove.testing.e2e.system.abstractions.*
import com.trendyol.stove.testing.e2e.wiremock.*
import io.kotest.core.config.AbstractProjectConfig
import org.slf4j.*
import stove.ktor.example.app.objectMapperRef
import kotlin.reflect.KClass

class GitlabStateStorageFactory : StateStorageFactory {
override fun <T : Any> invoke(
options: TestSystemOptions,
system: KClass<*>,
state: KClass<T>
): StateStorage<T> = object : StateStorage<T> {
override suspend fun capture(start: suspend () -> T): T {
return start()
}

override fun isSubsequentRun(): Boolean {
return false
}
}
}

class TestSystemConfig : AbstractProjectConfig() {
private val logger: Logger = LoggerFactory.getLogger("WireMockMonitor")

override suspend fun beforeProject() =
TestSystem(baseUrl = "http://localhost:8080")
.with {
httpClient()
bridge()
postgresql {
PostgresqlOptions(configureExposedConfiguration = { cfg ->
listOf(
"database.jdbcUrl=${cfg.jdbcUrl}",
"database.host=${cfg.host}",
"database.port=${cfg.port}",
"database.name=${cfg.database}",
"database.username=${cfg.username}",
"database.password=${cfg.password}"
)
})
}
kafka {
stoveKafkaObjectMapperRef = objectMapperRef
KafkaSystemOptions {
listOf(
"kafka.bootstrapServers=${it.bootstrapServers}",
"kafka.interceptorClasses=${it.interceptorClass}"
)
}
}
wiremock {
WireMockSystemOptions(
port = 9090,
removeStubAfterRequestMatched = true,
afterRequest = { e, _ ->
logger.info(e.request.toString())
}
TestSystem(baseUrl = "http://localhost:8080") {
// this.stateStorage(GitlabStateStorageFactory())
if (this.isRunningLocally()) {
enableReuseForTestContainers()
keepDependenciesRunning()
}
}.with {
httpClient()
bridge()
postgresql {
PostgresqlOptions(configureExposedConfiguration = { cfg ->
listOf(
"database.jdbcUrl=${cfg.jdbcUrl}",
"database.host=${cfg.host}",
"database.port=${cfg.port}",
"database.name=${cfg.database}",
"database.username=${cfg.username}",
"database.password=${cfg.password}"
)
})
}
kafka {
stoveKafkaObjectMapperRef = objectMapperRef
KafkaSystemOptions {
listOf(
"kafka.bootstrapServers=${it.bootstrapServers}",
"kafka.interceptorClasses=${it.interceptorClass}"
)
}
ktor(
withParameters = listOf(
"port=8080"
),
runner = { parameters ->
stove.ktor.example.run(parameters) {
addTestSystemDependencies()
}
}
wiremock {
WireMockSystemOptions(
port = 9090,
removeStubAfterRequestMatched = true,
afterRequest = { e, _ ->
logger.info(e.request.toString())
}
)
}.run()
}
ktor(
withParameters = listOf(
"port=8080"
),
runner = { parameters ->
stove.ktor.example.run(parameters) {
addTestSystemDependencies()
}
}
)
}.run()

override suspend fun afterProject() {
TestSystem.stop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@ class CouchbaseSystem internal constructor(

private lateinit var exposedConfiguration: CouchbaseExposedConfiguration
private val logger: Logger = LoggerFactory.getLogger(javaClass)
private val state: StateOfSystem<CouchbaseSystem, CouchbaseExposedConfiguration> = StateOfSystem(
testSystem.options,
CouchbaseSystem::class,
CouchbaseExposedConfiguration::class
)
private val state: StateStorage<CouchbaseExposedConfiguration> =
testSystem.options.createStateStorage<CouchbaseExposedConfiguration, CouchbaseSystem>()

override suspend fun run() {
exposedConfiguration =
Expand All @@ -48,7 +45,7 @@ class CouchbaseSystem internal constructor(

cluster = createCluster(exposedConfiguration)
collection = cluster.bucket(context.bucket.name).defaultCollection()
if (!state.isSubsequentRun()) {
if (!state.isSubsequentRun() || testSystem.options.runMigrationsAlways) {
context.options.migrationCollection.run(cluster)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class ElasticsearchSystem internal constructor(

private lateinit var exposedConfiguration: ElasticSearchExposedConfiguration
private val logger: Logger = LoggerFactory.getLogger(javaClass)
private val state: StateOfSystem<ElasticsearchSystem, ElasticSearchExposedConfiguration> =
StateOfSystem(testSystem.options, ElasticsearchSystem::class, ElasticSearchExposedConfiguration::class)
private val state: StateStorage<ElasticSearchExposedConfiguration> =
testSystem.options.createStateStorage<ElasticSearchExposedConfiguration, ElasticsearchSystem>()

override suspend fun run() {
exposedConfiguration = state.capture {
Expand All @@ -57,7 +57,7 @@ class ElasticsearchSystem internal constructor(

override suspend fun afterRun() {
esClient = createEsClient(exposedConfiguration)
if (!state.isSubsequentRun()) {
if (!state.isSubsequentRun() || testSystem.options.runMigrationsAlways) {
context.options.migrationCollection.run(esClient)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,8 @@ class KafkaSystem(
@PublishedApi
internal lateinit var sink: TestSystemMessageSink
private val logger: Logger = LoggerFactory.getLogger(javaClass)
private val state: StateOfSystem<KafkaSystem, KafkaExposedConfiguration> = StateOfSystem(
testSystem.options,
KafkaSystem::class,
KafkaExposedConfiguration::class
)
private val state: StateStorage<KafkaExposedConfiguration> =
testSystem.options.createStateStorage<KafkaExposedConfiguration, KafkaSystem>()

override suspend fun run() {
exposedConfiguration = state.capture {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ import org.bson.json.JsonWriterSettings
import org.bson.types.ObjectId
import org.slf4j.*
import reactor.kotlin.core.publisher.toFlux
import kotlin.collections.List
import kotlin.collections.MutableMap
import kotlin.collections.isNullOrEmpty
import kotlin.collections.listOf
import kotlin.collections.plus
import kotlin.collections.set

@MongoDsl
Expand All @@ -37,8 +32,8 @@ class MongodbSystem internal constructor(
JsonWriterSettings.builder()
.objectIdConverter { value, writer -> writer.writeString(value.toHexString()) }
.build()
private val state: StateOfSystem<MongodbSystem, MongodbExposedConfiguration> =
StateOfSystem(testSystem.options, MongodbSystem::class, MongodbExposedConfiguration::class)
private val state: StateStorage<MongodbExposedConfiguration> =
testSystem.options.createStateStorage<MongodbExposedConfiguration, MongodbSystem>()

override suspend fun run() {
exposedConfiguration =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class MsSqlSystem internal constructor(
private lateinit var sqlOperations: SqlOperations
private lateinit var exposedConfiguration: RelationalDatabaseExposedConfiguration
private val logger: Logger = LoggerFactory.getLogger(javaClass)
private val state: StateOfSystem<MsSqlSystem, RelationalDatabaseExposedConfiguration> =
StateOfSystem(testSystem.options, javaClass.kotlin, RelationalDatabaseExposedConfiguration::class)
private val state: StateStorage<RelationalDatabaseExposedConfiguration> =
testSystem.options.createStateStorage<RelationalDatabaseExposedConfiguration, MsSqlSystem>()

override suspend fun run() {
exposedConfiguration =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ abstract class RelationalDatabaseSystem<SELF : RelationalDatabaseSystem<SELF>> p
protected lateinit var exposedConfiguration: RelationalDatabaseExposedConfiguration

protected lateinit var sqlOperations: SqlOperations
protected val state: StateOfSystem<RelationalDatabaseSystem<SELF>, RelationalDatabaseExposedConfiguration> =
StateOfSystem(testSystem.options, javaClass.kotlin, RelationalDatabaseExposedConfiguration::class)
private val state: StateStorage<RelationalDatabaseExposedConfiguration> =
testSystem.options.createStateStorage<RelationalDatabaseExposedConfiguration, RelationalDatabaseSystem<SELF>>()

protected abstract fun connectionFactory(exposedConfiguration: RelationalDatabaseExposedConfiguration): ConnectionFactory

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class RedisSystem(
private lateinit var client: RedisClient
private lateinit var exposedConfiguration: RedisExposedConfiguration
private val logger: Logger = LoggerFactory.getLogger(javaClass)
private val state: StateOfSystem<RedisSystem, RedisExposedConfiguration> =
StateOfSystem(testSystem.options, RedisSystem::class, RedisExposedConfiguration::class)
private val state: StateStorage<RedisExposedConfiguration> =
testSystem.options.createStateStorage<RedisExposedConfiguration, RedisSystem>()

override suspend fun run() {
exposedConfiguration =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
package com.trendyol.stove.testing.e2e.system

data class TestSystemOptions(val keepDependenciesRunning: Boolean = false)
import com.trendyol.stove.testing.e2e.system.abstractions.*

data class TestSystemOptions(
val keepDependenciesRunning: Boolean = false,
val stateStorageFactory: StateStorageFactory = DefaultStateStorageFactory(),
val runMigrationsAlways: Boolean = false
) {
inline fun <reified TState : ExposedConfiguration, reified TSystem : PluggedSystem> createStateStorage(): StateStorage<TState> =
(this.stateStorageFactory(this, TSystem::class, TState::class))
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.trendyol.stove.testing.e2e.system

import com.trendyol.stove.testing.e2e.system.abstractions.StateStorageFactory
import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.*

@StoveDsl
class TestSystemOptionsDsl {
Expand Down Expand Up @@ -31,6 +31,18 @@ class TestSystemOptionsDsl {
@StoveDsl
fun enableReuseForTestContainers(): Unit = propertiesFile.enable()

@StoveDsl
fun stateStorage(factory: StateStorageFactory): TestSystemOptionsDsl {
options = options.copy(stateStorageFactory = factory)
return this
}

@StoveDsl
fun runMigrationsAlways(): TestSystemOptionsDsl {
options = options.copy(runMigrationsAlways = true)
return this
}

private fun isRunningOnCI(): Boolean =
System.getenv("CI") == "true" ||
System.getenv("GITLAB_CI") == "true" ||
Expand Down
Loading

0 comments on commit dbc2089

Please sign in to comment.