diff --git a/build.gradle.kts b/build.gradle.kts index 6228aa0c..acdb905d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,13 +32,15 @@ val projectDesc = "Fabricates Kotlin code from OpenApi3 specifications" val projectLicenseName = "Apache License 2.0" val projectLicenseUrl = "https://opensource.org/licenses/Apache-2.0" -repositories { - mavenCentral() +allprojects { + repositories { + mavenCentral() + } } +val jacksonVersion by extra { "2.15.1" } +val junitVersion by extra { "5.9.2" } dependencies { - val jacksonVersion = "2.15.1" - implementation(platform("org.jetbrains.kotlin:kotlin-bom")) implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.github.jknack:handlebars:4.3.1") @@ -53,9 +55,9 @@ dependencies { implementation("com.squareup:kotlinpoet:1.14.2") { exclude(module = "kotlin-stdlib-jre7") } implementation("com.google.flogger:flogger:0.7.4") - testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2") - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") - testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.2") + 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") // Below dependencies are solely present so code examples in the test resources dir compile diff --git a/end2end-tests/build.gradle.kts b/end2end-tests/build.gradle.kts new file mode 100644 index 00000000..bfefe5d4 --- /dev/null +++ b/end2end-tests/build.gradle.kts @@ -0,0 +1,72 @@ +val fabrikt: Configuration by configurations.creating + +val generationDir = "$buildDir/generated" +val apiFile = "$buildDir/../../src/test/resources/examples/okHttpClient/api.yaml" + +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 + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.10.0") + implementation("io.github.resilience4j:resilience4j-circuitbreaker:2.1.0") + 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") + + 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") + testImplementation("org.wiremock:wiremock:3.3.1") + testImplementation("com.marcinziolo:kotlin-wiremock:2.1.1") +} + +tasks { + + val generateCode by creating(JavaExec::class) { + 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", "client", + "--http-client-opts", "resilience4j" + ) + dependsOn(":jar") + dependsOn(":shadowJar") + } + + withType { + kotlinOptions.jvmTarget = "17" + dependsOn(generateCode) + } + + + withType { + useJUnitPlatform() + jvmArgs = listOf("--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.util=ALL-UNNAMED") + + } +} \ No newline at end of file diff --git a/end2end-tests/src/test/kotlin/com/cjbooms/fabrikt/clients/okio3/Okio3Test.kt b/end2end-tests/src/test/kotlin/com/cjbooms/fabrikt/clients/okio3/Okio3Test.kt new file mode 100644 index 00000000..4815f3ba --- /dev/null +++ b/end2end-tests/src/test/kotlin/com/cjbooms/fabrikt/clients/okio3/Okio3Test.kt @@ -0,0 +1,269 @@ +package com.cjbooms.fabrikt.clients.okio3 + +import com.example.client.* +import com.example.models.Failure +import com.example.models.FirstModel +import com.example.models.QueryResult +import com.fasterxml.jackson.databind.ObjectMapper +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.common.ConsoleNotifier +import com.github.tomakehurst.wiremock.core.WireMockConfiguration.options +import com.marcinziolo.kotlin.wiremock.* +import okhttp3.OkHttpClient +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.* +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.net.ServerSocket +import java.util.* +import java.util.stream.Stream + + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class Okio3Test { + private val port: Int = ServerSocket(0).use { socket -> socket.localPort } + + private val wiremock: WireMockServer = WireMockServer(options().port(port).notifier(ConsoleNotifier(true))) + + private val mapper = ObjectMapper() + private val httpClient = OkHttpClient.Builder().build() + private val examplePath1Client = ExamplePath1Client(mapper, "http://localhost:$port", httpClient) + private val examplePath2Client = ExamplePath2Client(mapper, "http://localhost:$port", httpClient) + private val examplePath3Client = ExamplePath3SubresourceClient(mapper, "http://localhost:$port", httpClient) + + private val uuid = UUID.randomUUID() + private val failure = Failure(traceId = uuid, + error = "testError", + errorCode = "testErrorCode") + + @Suppress("unused") + private fun path2ErrorCodes(): Stream = Stream.of(400, 422, 423) + + @BeforeEach + fun setUp() { + wiremock.start() + } + + @AfterEach + fun afterEach() { + wiremock.resetAll() + wiremock.stop() + } + + @Test + fun `throws an exception if 404 is returned`() { + wiremock.get { + url like "/example-path-1" + } returns { + statusCode = 404 + } + + val result = assertThrows { + examplePath1Client.getExamplePath1() + } + assertThat(result.statusCode).isEqualTo(404) + } + + @Test + fun `returns data when no query parameters are send`(testInfo: TestInfo) { + wiremock.get { + url like "/example-path-1" + } returns { + statusCode = 200 + body = mapper.writeValueAsString( + QueryResult( + listOf(FirstModel(id = testInfo.displayName)) + ) + ) + } + + val result = examplePath1Client.getExamplePath1() + + assertThat(result.data).isEqualTo( + QueryResult( + listOf(FirstModel(id = testInfo.displayName)) + ) + ) + } + + @Test + fun `adds query_param2 to the query`(testInfo: TestInfo) { + wiremock.get { + urlPath like "/example-path-1" + queryParams contains "query_param2" like "10" + } returns { + statusCode = 200 + body = mapper.writeValueAsString( + QueryResult( + listOf(FirstModel(id = testInfo.displayName)) + ) + ) + } + + val result = examplePath1Client.getExamplePath1(queryParam2 = 10) + + assertThat(result.data).isEqualTo( + QueryResult( + listOf(FirstModel(id = testInfo.displayName)) + ) + ) + } + + @Test + fun `adds explode_list_query_param to the query`(testInfo: TestInfo) { + wiremock.get { + urlPath like "/example-path-1" + queryParams contains "explode_list_query_param" like "list" + queryParams contains "explode_list_query_param" like "of" + queryParams contains "explode_list_query_param" like "parameters" + } returns { + statusCode = 200 + body = mapper.writeValueAsString( + QueryResult( + listOf(FirstModel(id = testInfo.displayName)) + ) + ) + } + + val result = examplePath1Client.getExamplePath1(explodeListQueryParam = listOf("list", "of", "parameters")) + + assertThat(result.data).isEqualTo( + QueryResult( + listOf(FirstModel(id = testInfo.displayName)) + ) + ) + } + + @Test + fun `adds additional headers to the query`(testInfo: TestInfo) { + wiremock.get { + urlPath like "/example-path-1" + headers contains "awesome" like "header" + } returns { + statusCode = 200 + body = mapper.writeValueAsString( + QueryResult( + listOf(FirstModel(id = testInfo.displayName)) + ) + ) + } + + val result = examplePath1Client.getExamplePath1(additionalHeaders = mapOf("awesome" to "header")) + + assertThat(result.data).isEqualTo( + QueryResult( + listOf(FirstModel(id = testInfo.displayName)) + ) + ) + } + + @Test + fun `send body with post request`(testInfo: TestInfo) { + val content = FirstModel(id = testInfo.displayName) + wiremock.post { + urlPath like "/example-path-1" + body equalTo mapper.writeValueAsString(content) + } returns { + statusCode = 201 + } + + val result = examplePath1Client.postExamplePath1(content) + assertThat(result.statusCode).isEqualTo(201) + } + + @ParameterizedTest + @MethodSource("path2ErrorCodes") + fun `throws an exception if a 4xx http status code is returned`(errorCode: Int) { + wiremock.get { + urlPath like "/example-path-2/$errorCode" + } returns { + statusCode = errorCode + body = mapper.writeValueAsString(failure) + } + + val result = assertThrows { + examplePath2Client.getExamplePath2PathParam(errorCode.toString(), 10) + } + + assertThat(result.statusCode).isEqualTo(errorCode) + assertThat(mapper.readValue(result.message, Failure::class.java)).isEqualTo(failure) + } + + @Test + fun `throws an exception if a http status code 500 is returned`() { + wiremock.get { + urlPath like "/example-path-2/500" + } returns { + statusCode = 500 + body = mapper.writeValueAsString(failure) + } + + val result = assertThrows { + examplePath2Client.getExamplePath2PathParam("500", 10) + } + + assertThat(result.statusCode).isEqualTo(500) + assertThat(mapper.readValue(result.message, Failure::class.java)).isEqualTo(failure) + } + + @Test + fun `throws an exception if a http status code 304 is returned`() { + wiremock.get { + urlPath like "/example-path-2/304" + } returns { + statusCode = 304 + } + + val result = assertThrows { + examplePath2Client.getExamplePath2PathParam("304", 10) + } + + assertThat(result.statusCode).isEqualTo(304) + } + + + @Test + fun `head returns 200`() { + wiremock.head { + urlPath like "/example-path-2/head200" + } returns { + statusCode = 200 + } + + val result = examplePath2Client.headOperationIdExample("head200") + + assertThat(result.statusCode).isEqualTo(200) + } + + @Test + fun `put returns 204`() { + val model = FirstModel(id = "put", secondAttr = "204") + wiremock.put { + urlPath like "/example-path-2/put204" + body equalTo mapper.writeValueAsString(model) + headers contains "If-Match" like "match" + } returns { + statusCode = 204 + } + + val result = examplePath2Client.putExamplePath2PathParam(firstModel = model, pathParam = "put204", ifMatch = "match") + + assertThat(result.statusCode).isEqualTo(204) + } + + @Test + fun `put returns 204 with sub resource`() { + val model = FirstModel(id = "put", secondAttr = "304") + wiremock.put { + urlPath like "/example-path-3/put304/subresource" + body equalTo mapper.writeValueAsString(model) + headers contains "If-Match" like "match" + } returns { + statusCode = 204 + } + + val result = examplePath3Client.putExamplePath3PathParamSubresource(firstModel = model, pathParam = "put304", ifMatch = "match") + + assertThat(result.statusCode).isEqualTo(204) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0a6cd528..1772b321 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,3 @@ rootProject.name = "fabrikt" + +include("end2end-tests")