Implement first routes & tests
install Koin install kotest declare first events create EventStream
This commit is contained in:
17
.run/ApplicationKt.run.xml
Normal file
17
.run/ApplicationKt.run.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="ApplicationKt" type="KtorApplicationConfigurationType" factoryName="Ktor" nameIsGenerated="true">
|
||||||
|
<module name="event-demo.main" />
|
||||||
|
<option name="alternativeJrePath" />
|
||||||
|
<option name="alternativeJrePathEnabled" value="false" />
|
||||||
|
<option name="includeProvidedScope" value="true" />
|
||||||
|
<option name="mainClass" value="eventDemo.ApplicationKt" />
|
||||||
|
<option name="passParentEnvs" value="true" />
|
||||||
|
<option name="programParameters" value="" />
|
||||||
|
<option name="shortenCommandLine" value="NONE" />
|
||||||
|
<option name="vmParameters" value="-Dio.ktor.development=true" />
|
||||||
|
<option name="workingDirectory" value="$PROJECT_DIR$" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.run/ktlintFormat.run.xml
Normal file
24
.run/ktlintFormat.run.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="ktlintFormat" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="ktlintFormat" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.run/test.run.xml
Normal file
24
.run/test.run.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="test" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="test" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
|
@file:Suppress("PropertyName")
|
||||||
|
|
||||||
@Suppress("ktlint:standard:property-naming")
|
@Suppress("ktlint:standard:property-naming")
|
||||||
val ktor_version: String by project
|
val ktor_version: String by project
|
||||||
val kotlin_version: String by project
|
val kotlin_version: String by project
|
||||||
val logback_version: String by project
|
val logback_version: String by project
|
||||||
|
val koin_version: String by project
|
||||||
|
val kotlin_logging_version: String by project
|
||||||
|
val kotest_version: String by project
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.9.22"
|
kotlin("jvm") version "1.9.22"
|
||||||
@@ -14,7 +19,7 @@ group = "io.github.flecomte"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("io.github.flecomte.ApplicationKt")
|
mainClass.set("eventDemo.ApplicationKt")
|
||||||
|
|
||||||
val isDevelopment: Boolean = project.ext.has("development")
|
val isDevelopment: Boolean = project.ext.has("development")
|
||||||
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
|
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
|
||||||
@@ -24,6 +29,10 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test>().configureEach {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.ktor:ktor-server-core-jvm")
|
implementation("io.ktor:ktor-server-core-jvm")
|
||||||
implementation("io.ktor:ktor-server-auth-jvm")
|
implementation("io.ktor:ktor-server-auth-jvm")
|
||||||
@@ -37,7 +46,14 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-server-host-common-jvm")
|
implementation("io.ktor:ktor-server-host-common-jvm")
|
||||||
implementation("io.ktor:ktor-server-status-pages-jvm")
|
implementation("io.ktor:ktor-server-status-pages-jvm")
|
||||||
implementation("io.ktor:ktor-server-netty-jvm")
|
implementation("io.ktor:ktor-server-netty-jvm")
|
||||||
|
implementation("io.ktor:ktor-server-data-conversion")
|
||||||
|
implementation("io.ktor:ktor-client-content-negotiation")
|
||||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
implementation("ch.qos.logback:logback-classic:$logback_version")
|
||||||
|
implementation("io.insert-koin:koin-ktor:$koin_version")
|
||||||
|
implementation("io.insert-koin:koin-logger-slf4j:$koin_version")
|
||||||
|
implementation("io.github.oshai:kotlin-logging-jvm:$kotlin_logging_version")
|
||||||
testImplementation("io.ktor:ktor-server-tests-jvm")
|
testImplementation("io.ktor:ktor-server-tests-jvm")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
|
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
|
||||||
|
testImplementation("io.ktor:ktor-server-test-host-jvm:2.3.8")
|
||||||
|
testImplementation("io.kotest:kotest-runner-junit5:$kotest_version")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
ktor_version=2.3.8
|
ktor_version=2.3.8
|
||||||
kotlin_version=1.9.22
|
kotlin_version=1.9.22
|
||||||
logback_version=1.4.14
|
logback_version=1.4.14
|
||||||
|
koin_version=3.5.3
|
||||||
|
kotlin_logging_version=5.1.0
|
||||||
|
kotest_version=5.8.0
|
||||||
|
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package io.github.flecomte
|
package eventDemo
|
||||||
|
|
||||||
import io.github.flecomte.plugins.configureHTTP
|
import eventDemo.plugins.configureHTTP
|
||||||
import io.github.flecomte.plugins.configureRouting
|
import eventDemo.plugins.configureKoin
|
||||||
import io.github.flecomte.plugins.configureSecurity
|
import eventDemo.plugins.configureRouting
|
||||||
import io.github.flecomte.plugins.configureSerialization
|
import eventDemo.plugins.configureSecurity
|
||||||
import io.github.flecomte.plugins.configureSockets
|
import eventDemo.plugins.configureSerialization
|
||||||
|
import eventDemo.plugins.configureSockets
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
|
embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module, watchPaths = listOf("classes"))
|
||||||
.start(wait = true)
|
.start(wait = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,4 +21,5 @@ fun Application.module() {
|
|||||||
configureSockets()
|
configureSockets()
|
||||||
configureHTTP()
|
configureHTTP()
|
||||||
configureRouting()
|
configureRouting()
|
||||||
|
configureKoin()
|
||||||
}
|
}
|
||||||
14
src/main/kotlin/eventDemo/app/AggregateId.kt
Normal file
14
src/main/kotlin/eventDemo/app/AggregateId.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package eventDemo.app
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
sealed interface AggregateId {
|
||||||
|
val id: UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class GameId(override val id: UUID = UUID.randomUUID()) : AggregateId {
|
||||||
|
constructor(id: String) : this(UUID.fromString(id))
|
||||||
|
|
||||||
|
override fun toString(): String = id.toString()
|
||||||
|
}
|
||||||
30
src/main/kotlin/eventDemo/app/Card.kt
Normal file
30
src/main/kotlin/eventDemo/app/Card.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package eventDemo.app
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed interface Card {
|
||||||
|
@Serializable
|
||||||
|
enum class Color {
|
||||||
|
Blue,
|
||||||
|
Red,
|
||||||
|
Yellow,
|
||||||
|
Green,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("Simple")
|
||||||
|
data class Simple(
|
||||||
|
val number: Int,
|
||||||
|
val color: Color,
|
||||||
|
) : Card
|
||||||
|
|
||||||
|
sealed interface Special : Card
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("Reverse")
|
||||||
|
data class ReverseCard(
|
||||||
|
val color: Color,
|
||||||
|
) : Special
|
||||||
|
}
|
||||||
27
src/main/kotlin/eventDemo/app/EventStream.kt
Normal file
27
src/main/kotlin/eventDemo/app/EventStream.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package eventDemo.app
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
|
||||||
|
class EventStream<ID : AggregateId> {
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
private val eventBus: MutableMap<ID, MutableList<Event<ID>>> = mutableMapOf()
|
||||||
|
|
||||||
|
fun publish(event: Event<ID>) {
|
||||||
|
eventBus.getOrPut(event.aggregateId) { mutableListOf() }.add(event)
|
||||||
|
logger.atInfo {
|
||||||
|
message = "Event published"
|
||||||
|
payload = mapOf("event" to event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <U : Event<ID>> read(
|
||||||
|
aggregateId: ID,
|
||||||
|
eventClass: Class<U>,
|
||||||
|
): U? {
|
||||||
|
return eventBus.get(aggregateId)?.filterIsInstance(eventClass)?.firstOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified U : Event<ID>, ID : AggregateId> EventStream<ID>.read(aggregateId: ID): U? {
|
||||||
|
return this.read(aggregateId, U::class.java)
|
||||||
|
}
|
||||||
10
src/main/kotlin/eventDemo/app/Events.kt
Normal file
10
src/main/kotlin/eventDemo/app/Events.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package eventDemo.app
|
||||||
|
|
||||||
|
sealed interface Event<ID : AggregateId> {
|
||||||
|
val aggregateId: ID
|
||||||
|
}
|
||||||
|
|
||||||
|
data class PlayCardEvent(
|
||||||
|
override val aggregateId: GameId,
|
||||||
|
val card: Card,
|
||||||
|
) : Event<GameId>
|
||||||
54
src/main/kotlin/eventDemo/app/actions/PutCard.kt
Normal file
54
src/main/kotlin/eventDemo/app/actions/PutCard.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package eventDemo.app.actions
|
||||||
|
|
||||||
|
import eventDemo.app.Card
|
||||||
|
import eventDemo.app.EventStream
|
||||||
|
import eventDemo.app.GameId
|
||||||
|
import eventDemo.app.PlayCardEvent
|
||||||
|
import eventDemo.app.read
|
||||||
|
import eventDemo.plugins.GameIdSerializer
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.resources.Resource
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.request.receive
|
||||||
|
import io.ktor.server.resources.get
|
||||||
|
import io.ktor.server.resources.post
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondNullable
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.koin.ktor.ext.inject
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@Resource("/game/{id}")
|
||||||
|
class Game(
|
||||||
|
@Serializable(with = GameIdSerializer::class)
|
||||||
|
val id: GameId,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
@Resource("card")
|
||||||
|
class Card(val game: Game) {
|
||||||
|
@Serializable
|
||||||
|
@Resource("")
|
||||||
|
class PutCard(val card: Card)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@Resource("last")
|
||||||
|
class LastCard(val card: Card)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Routing.putCard() {
|
||||||
|
val eventStream by inject<EventStream<GameId>>()
|
||||||
|
|
||||||
|
post<Game.Card.PutCard> {
|
||||||
|
val card = call.receive<Card.Simple>()
|
||||||
|
eventStream.publish(PlayCardEvent(it.card.game.id, card))
|
||||||
|
call.respondNullable<Any?>(HttpStatusCode.OK, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
get<Game.Card.LastCard> {
|
||||||
|
eventStream.read<PlayCardEvent, GameId>(it.card.game.id)
|
||||||
|
?.let { it1 -> call.respond<Card>(it1.card) }
|
||||||
|
?: call.response.status(HttpStatusCode.BadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package io.github.flecomte.plugins
|
package eventDemo.plugins
|
||||||
|
|
||||||
import io.ktor.http.HttpHeaders
|
import io.ktor.http.HttpHeaders
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.plugins.cors.routing.CORS
|
import io.ktor.server.plugins.cors.routing.CORS
|
||||||
@@ -10,6 +11,7 @@ fun Application.configureHTTP() {
|
|||||||
install(CORS) {
|
install(CORS) {
|
||||||
allowMethod(HttpMethod.Options)
|
allowMethod(HttpMethod.Options)
|
||||||
allowMethod(HttpMethod.Put)
|
allowMethod(HttpMethod.Put)
|
||||||
|
allowMethod(HttpMethod.Post)
|
||||||
allowMethod(HttpMethod.Delete)
|
allowMethod(HttpMethod.Delete)
|
||||||
allowMethod(HttpMethod.Patch)
|
allowMethod(HttpMethod.Patch)
|
||||||
allowHeader(HttpHeaders.Authorization)
|
allowHeader(HttpHeaders.Authorization)
|
||||||
@@ -17,3 +19,18 @@ fun Application.configureHTTP() {
|
|||||||
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
|
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BadRequestException(val httpError: HttpErrorBadRequest) : Exception()
|
||||||
|
|
||||||
|
class HttpErrorBadRequest(
|
||||||
|
statusCode: HttpStatusCode,
|
||||||
|
val title: String = statusCode.description,
|
||||||
|
val invalidParams: List<InvalidParam>,
|
||||||
|
) {
|
||||||
|
val statusCode: Int = statusCode.value
|
||||||
|
|
||||||
|
data class InvalidParam(
|
||||||
|
val name: String,
|
||||||
|
val reason: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
22
src/main/kotlin/eventDemo/plugins/Koin.kt
Normal file
22
src/main/kotlin/eventDemo/plugins/Koin.kt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package eventDemo.plugins
|
||||||
|
|
||||||
|
import eventDemo.app.EventStream
|
||||||
|
import eventDemo.app.GameId
|
||||||
|
import io.ktor.server.application.Application
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.ktor.plugin.Koin
|
||||||
|
import org.koin.logger.slf4jLogger
|
||||||
|
|
||||||
|
fun Application.configureKoin() {
|
||||||
|
install(Koin) {
|
||||||
|
slf4jLogger()
|
||||||
|
modules(appModule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val appModule =
|
||||||
|
module {
|
||||||
|
singleOf<EventStream<GameId>>(::EventStream)
|
||||||
|
}
|
||||||
@@ -1,39 +1,31 @@
|
|||||||
package io.github.flecomte.plugins
|
package eventDemo.plugins
|
||||||
|
|
||||||
|
import eventDemo.app.actions.putCard
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.resources.Resource
|
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.plugins.autohead.AutoHeadResponse
|
import io.ktor.server.plugins.autohead.AutoHeadResponse
|
||||||
import io.ktor.server.plugins.statuspages.StatusPages
|
import io.ktor.server.plugins.statuspages.StatusPages
|
||||||
import io.ktor.server.resources.Resources
|
import io.ktor.server.resources.Resources
|
||||||
import io.ktor.server.resources.get
|
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.response.respondText
|
import io.ktor.server.response.respondText
|
||||||
import io.ktor.server.routing.get
|
|
||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
import kotlinx.serialization.Serializable
|
import io.ktor.util.converters.DataConversion.Configuration
|
||||||
|
|
||||||
fun Application.configureRouting() {
|
fun Application.configureRouting() {
|
||||||
install(AutoHeadResponse)
|
install(AutoHeadResponse)
|
||||||
install(Resources)
|
install(Resources)
|
||||||
install(StatusPages) {
|
install(StatusPages) {
|
||||||
|
exception<BadRequestException> { call, cause ->
|
||||||
|
call.respondText(text = "400: $cause", status = HttpStatusCode.BadRequest)
|
||||||
|
}
|
||||||
exception<Throwable> { call, cause ->
|
exception<Throwable> { call, cause ->
|
||||||
call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError)
|
call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routing {
|
routing {
|
||||||
get("/") {
|
putCard()
|
||||||
call.respondText("Hello World!")
|
|
||||||
}
|
|
||||||
get<Articles> { article ->
|
|
||||||
// Get all articles ...
|
|
||||||
call.respond("List of articles sorted starting from ${article.sort}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
private typealias ConverterDeclaration = Configuration.() -> Unit
|
||||||
@Resource("/articles")
|
|
||||||
class Articles(val sort: String? = "new")
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package io.github.flecomte.plugins
|
package eventDemo.plugins
|
||||||
|
|
||||||
import com.auth0.jwt.JWT
|
import com.auth0.jwt.JWT
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
55
src/main/kotlin/eventDemo/plugins/Serialization.kt
Normal file
55
src/main/kotlin/eventDemo/plugins/Serialization.kt
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package eventDemo.plugins
|
||||||
|
|
||||||
|
import eventDemo.app.GameId
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.Application
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
fun Application.configureSerialization() {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(
|
||||||
|
Json {
|
||||||
|
serializersModule =
|
||||||
|
SerializersModule {
|
||||||
|
contextual(UUID::class) { UUIDSerializer }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object GameIdSerializer : KSerializer<GameId> {
|
||||||
|
override fun deserialize(decoder: Decoder): GameId = GameId(UUID.fromString(decoder.decodeString()))
|
||||||
|
|
||||||
|
override fun serialize(
|
||||||
|
encoder: Encoder,
|
||||||
|
value: GameId,
|
||||||
|
) {
|
||||||
|
encoder.encodeString(value.id.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("GameId", PrimitiveKind.STRING)
|
||||||
|
}
|
||||||
|
|
||||||
|
object UUIDSerializer : KSerializer<UUID> {
|
||||||
|
override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
|
||||||
|
|
||||||
|
override fun serialize(
|
||||||
|
encoder: Encoder,
|
||||||
|
value: UUID,
|
||||||
|
) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package io.github.flecomte.plugins
|
package eventDemo.plugins
|
||||||
|
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package io.github.flecomte.plugins
|
|
||||||
|
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
|
||||||
import io.ktor.server.application.Application
|
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.application.install
|
|
||||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.routing.get
|
|
||||||
import io.ktor.server.routing.routing
|
|
||||||
|
|
||||||
fun Application.configureSerialization() {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json()
|
|
||||||
}
|
|
||||||
routing {
|
|
||||||
get("/json/kotlinx-serialization") {
|
|
||||||
call.respond(mapOf("hello" to "world"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
src/test/kotlin/eventDemo/app/actions/Client.kt
Normal file
25
src/test/kotlin/eventDemo/app/actions/Client.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package eventDemo.app.actions
|
||||||
|
|
||||||
|
import eventDemo.plugins.UUIDSerializer
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.testing.ApplicationTestBuilder
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
fun ApplicationTestBuilder.httpClient(): HttpClient {
|
||||||
|
return createClient {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(
|
||||||
|
Json {
|
||||||
|
serializersModule =
|
||||||
|
SerializersModule {
|
||||||
|
contextual(UUID::class) { UUIDSerializer }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/test/kotlin/eventDemo/app/actions/PutCardTest.kt
Normal file
61
src/test/kotlin/eventDemo/app/actions/PutCardTest.kt
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package eventDemo.app.actions
|
||||||
|
|
||||||
|
import eventDemo.app.Card
|
||||||
|
import eventDemo.app.EventStream
|
||||||
|
import eventDemo.app.GameId
|
||||||
|
import eventDemo.app.PlayCardEvent
|
||||||
|
import eventDemo.module
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.request.accept
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.client.request.setBody
|
||||||
|
import io.ktor.http.ContentType.Application.Json
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.contentType
|
||||||
|
import io.ktor.server.testing.testApplication
|
||||||
|
import org.koin.core.context.stopKoin
|
||||||
|
import org.koin.ktor.ext.inject
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PutCardTest : FunSpec({
|
||||||
|
test("/game/{id}/card") {
|
||||||
|
testApplication {
|
||||||
|
val client = httpClient()
|
||||||
|
application {
|
||||||
|
stopKoin()
|
||||||
|
module()
|
||||||
|
}
|
||||||
|
val id = GameId().toString()
|
||||||
|
client.post("/game/$id/card") {
|
||||||
|
contentType(Json)
|
||||||
|
accept(Json)
|
||||||
|
setBody(Card.Simple(1, Card.Color.Blue))
|
||||||
|
}.apply {
|
||||||
|
assertEquals(status, HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("/game/{id}/card/last") {
|
||||||
|
testApplication {
|
||||||
|
val client = httpClient()
|
||||||
|
val id = GameId()
|
||||||
|
val card = Card.Simple(1, Card.Color.Blue)
|
||||||
|
application {
|
||||||
|
stopKoin()
|
||||||
|
module()
|
||||||
|
val eventStream by inject<EventStream<GameId>>()
|
||||||
|
eventStream.publish(
|
||||||
|
PlayCardEvent(id, card),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/game/$id/card/last").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(card, this.call.body<Card>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package io.github.flecomte
|
|
||||||
|
|
||||||
import io.github.flecomte.plugins.configureRouting
|
|
||||||
import io.ktor.client.request.get
|
|
||||||
import io.ktor.client.statement.bodyAsText
|
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.server.testing.testApplication
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class ApplicationTest {
|
|
||||||
@Test
|
|
||||||
fun testRoot() =
|
|
||||||
testApplication {
|
|
||||||
application {
|
|
||||||
configureRouting()
|
|
||||||
}
|
|
||||||
client.get("/").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals("Hello World!", bodyAsText())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user