Skip to content

Commit

Permalink
Add e2e tests for Ktor server interfaces (#290)
Browse files Browse the repository at this point in the history
  • Loading branch information
ulrikandersen authored Jul 10, 2024
1 parent 71103e3 commit 7f2ccb0
Show file tree
Hide file tree
Showing 10 changed files with 661 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The library currently has support for generating:

* **Jackson annotated data classes**
* **Spring MVC annotated controller interfaces**
* **Ktor server routes and controller interfaces**
* **Ktor server routes and controller interfaces ([examples](end2end-tests/ktor/src/test/kotlin/com/cjbooms/fabrikt/servers/ktor))**
* **OkHttp Client** - with the option for a resilience4j fault-tolerance wrapper

### Example Generation
Expand Down
6 changes: 4 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ allprojects {

val jacksonVersion by extra { "2.15.1" }
val junitVersion by extra { "5.9.2" }
val ktorVersion by extra { "2.3.9" }

dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
Expand Down Expand Up @@ -70,8 +72,8 @@ dependencies {
//testCompileOnly("io.micronaut.security:micronaut-security:3.8.7")
testImplementation("com.squareup.okhttp3:okhttp:4.10.0")
testImplementation("org.openapitools:jackson-databind-nullable:0.2.6")
testImplementation("io.ktor:ktor-server-core:2.3.9")
testImplementation("io.ktor:ktor-server-auth:2.3.9")
testImplementation("io.ktor:ktor-server-core:$ktorVersion")
testImplementation("io.ktor:ktor-server-auth:$ktorVersion")

testImplementation(platform("com.pinterest.ktlint:ktlint-bom:0.49.0"))
testImplementation("com.pinterest:ktlint")
Expand Down
97 changes: 97 additions & 0 deletions end2end-tests/ktor/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
val fabrikt: Configuration by configurations.creating

val generationDir = "$buildDir/generated"

sourceSets {
main { java.srcDirs("$generationDir/src/main/kotlin") }
test { java.srcDirs("$generationDir/src/test/kotlin") }
}

plugins {
id("org.jetbrains.kotlin.jvm") version "1.8.20" // Apply the Kotlin JVM plugin to add support for Kotlin.
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

val jacksonVersion: String by rootProject.extra
val junitVersion: String by rootProject.extra
val ktorVersion: String by rootProject.extra

dependencies {
implementation("jakarta.validation:jakarta.validation-api:3.0.2")
implementation("javax.validation:validation-api:2.0.1.Final")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
implementation("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")
implementation("com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion")

// ktor server
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktorVersion")
implementation("io.ktor:ktor-serialization-jackson:$ktorVersion")
implementation("io.ktor:ktor-server-auth:$ktorVersion")
implementation("io.ktor:ktor-server-status-pages:$ktorVersion")

// ktor test
testImplementation("io.ktor:ktor-server-test-host:$ktorVersion")
testImplementation("io.ktor:ktor-server-auth:$ktorVersion")
testImplementation("io.ktor:ktor-server-auth-jwt:$ktorVersion")
testImplementation("io.ktor:ktor-server-status-pages:$ktorVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20")

testImplementation("io.mockk:mockk:1.13.7")

testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
testImplementation("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
testImplementation("org.junit.jupiter:junit-jupiter-params:$junitVersion")
testImplementation("org.assertj:assertj-core:3.24.2")
}

tasks {
val generateKtorCode = createCodeGenerationTask(
"generateKtorCode",
"src/test/resources/examples/githubApi/api.yaml"
)

val generateKtorAuthCode = createCodeGenerationTask(
"generateKtorAuthCode",
"src/test/resources/examples/authentication/api.yaml",
listOf("--http-controller-opts", "AUTHENTICATION")
)

withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "17"
dependsOn(generateKtorCode)
dependsOn(generateKtorAuthCode)
}


withType<Test> {
useJUnitPlatform()
jvmArgs = listOf("--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.util=ALL-UNNAMED")
}
}

fun TaskContainer.createCodeGenerationTask(
name: String, apiFilePath: String, opts: List<String> = emptyList()
): TaskProvider<JavaExec> = register(name, JavaExec::class) {
val apiFile = "${rootProject.projectDir}/$apiFilePath"
inputs.files(apiFile)
outputs.dir(generationDir)
outputs.cacheIf { true }
classpath = rootProject.files("./build/libs/fabrikt-${rootProject.version}.jar")
mainClass.set("com.cjbooms.fabrikt.cli.CodeGen")
args = listOf(
"--output-directory", generationDir,
"--base-package", "com.example",
"--api-file", apiFile,
"--targets", "http_models",
"--targets", "controllers",
"--http-controller-target", "ktor",
) + opts
dependsOn(":jar")
dependsOn(":shadowJar")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.cjbooms.fabrikt.servers.ktor

import com.example.controllers.ContributorsController
import com.example.controllers.TypedApplicationCall
import com.example.models.Contributor
import com.example.models.ContributorQueryResult
import com.example.models.StatusQueryParam
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond
import io.mockk.CapturingSlot

/**
* This is a test implementation of the ContributorsController interface that captures the values of the parameters
* in CapturingSlots so that they can be asserted against in tests.
*/
class ContributorsControllerImpl(
private val xFlowIdCapturingSlot: CapturingSlot<String?>,
private val limitCapturingSlot: CapturingSlot<Int?>,
) : ContributorsController {
override suspend fun searchContributors(
xFlowId: String?,
limit: Int?,
includeInactive: Boolean?,
cursor: String?,
call: TypedApplicationCall<ContributorQueryResult>
) {
xFlowIdCapturingSlot.captured = xFlowId
limitCapturingSlot.captured = limit

call.respondTyped(ContributorQueryResult(null, null, emptyList()))
}

override suspend fun createContributor(
xFlowId: String?,
idempotencyKey: String?,
contributor: Contributor,
call: ApplicationCall
) {
call.respond(HttpStatusCode.Created)
}

override suspend fun getContributor(
xFlowId: String?,
ifNoneMatch: String?,
id: String,
status: StatusQueryParam?,
call: TypedApplicationCall<Contributor>
) {
// does not respond, should result in 404 Not Found
}

override suspend fun putById(
ifMatch: String,
xFlowId: String?,
idempotencyKey: String?,
id: String,
contributor: Contributor,
call: ApplicationCall
) {
call.respond(HttpStatusCode.NoContent)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.cjbooms.fabrikt.servers.ktor

import com.example.controllers.InternalEventsController
import com.example.controllers.TypedApplicationCall
import com.example.models.BulkEntityDetails
import com.example.models.Event
import com.example.models.EventResults
import io.ktor.http.HttpStatusCode
import io.mockk.CapturingSlot

/**
* This is a test implementation of the [InternalEventsController] interface that captures the request body
* and returns a fixed response.
*/
class InternalEventsControllerImpl(
private val bodyCapturingSlot: CapturingSlot<BulkEntityDetails>,
) : InternalEventsController {
override suspend fun post(bulkEntityDetails: BulkEntityDetails, call: TypedApplicationCall<EventResults>) {
bodyCapturingSlot.captured = bulkEntityDetails

call.respondTyped(
HttpStatusCode.OK, EventResults(
listOf(
Event(
entityId = "entityId",
properties = mutableMapOf(),
data = mapOf(
"dataKey" to 1,
"otherDataKey" to "value",
)
)
)
)
)
}
}
Loading

0 comments on commit 7f2ccb0

Please sign in to comment.