diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml index 1343aca..2caf377 100644 --- a/.github/workflows/build-action.yml +++ b/.github/workflows/build-action.yml @@ -7,18 +7,27 @@ jobs: build-job: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v2 + uses: actions/setup-java@v5 with: java-version: '11' - distribution: 'adopt' + distribution: 'temurin' cache: gradle + cache-dependency-path: | + **/gradle-wrapper.properties + **/build.gradle + **/build.gradle.kts + **/settings.gradle + **/settings.gradle.kts - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + uses: gradle/wrapper-validation-action@v1 + - name: Ensure gradlew is executable + run: chmod +x ./gradlew - name: build - run: ./gradlew clean build + run: ./gradlew --no-daemon clean build - name: Cleanup Gradle Cache + if: always() run: | - rm -f ~/.gradle/caches/modules-2/modules-2.lock - rm -f ~/.gradle/caches/modules-2/gc.properties + rm -f ~/.gradle/caches/modules-2/modules-2.lock || true + rm -f ~/.gradle/caches/modules-2/gc.properties || true diff --git a/.github/workflows/deploy-action.yml b/.github/workflows/deploy-action.yml index 184dca2..e9dc6a7 100644 --- a/.github/workflows/deploy-action.yml +++ b/.github/workflows/deploy-action.yml @@ -7,31 +7,41 @@ jobs: deploy-job: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v2 + uses: actions/setup-java@v5 with: java-version: '11' - distribution: 'adopt' + distribution: 'temurin' cache: gradle + cache-dependency-path: | + **/gradle-wrapper.properties + **/build.gradle + **/build.gradle.kts + **/settings.gradle + **/settings.gradle.kts - name: decode key env: - secringEncodingKey: ${{secrets.MC_SECRING_ENC_KEY}} + secringEncodingKey: ${{ secrets.MC_SECRING_ENC_KEY }} run: gpg --quiet --batch --yes --decrypt --passphrase="$secringEncodingKey" --output secring.gpg secring.gpg.gpgenc - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + uses: gradle/wrapper-validation-action@v1 + - name: Ensure gradlew is executable + run: chmod +x ./gradlew - name: deploy env: - repositoryUrl: ${{secrets.MC_REPOSITORY_URL}} - repositoryUser: ${{secrets.MC_REPOSITORY_USER}} - repositoryPassword: ${{secrets.MC_REPOSITORY_PASSWORD}} - signingKeyId: ${{secrets.MC_SIGNING_KEY_ID}} - signingPassword: ${{secrets.MC_SIGNING_PASSWORD}} - run: ./gradlew -PsigningSecretKeyRingFile="`pwd`/secring.gpg" -Dorg.gradle.internal.publish.checksums.insecure=true --info clean build publishToSonatype closeAndReleaseRepository + repositoryUrl: ${{ secrets.MC_REPOSITORY_URL }} + repositoryUser: ${{ secrets.MC_REPOSITORY_USER }} + repositoryPassword: ${{ secrets.MC_REPOSITORY_PASSWORD }} + signingKeyId: ${{ secrets.MC_SIGNING_KEY_ID }} + signingPassword: ${{ secrets.MC_SIGNING_PASSWORD }} + run: ./gradlew --no-daemon -PsigningSecretKeyRingFile="$(pwd)/secring.gpg" -Dorg.gradle.internal.publish.checksums.insecure=true --info clean build publishToSonatype closeAndReleaseRepository - name: Cleanup Gradle Cache + if: always() run: | - rm -f ~/.gradle/caches/modules-2/modules-2.lock - rm -f ~/.gradle/caches/modules-2/gc.properties + rm -f ~/.gradle/caches/modules-2/modules-2.lock || true + rm -f ~/.gradle/caches/modules-2/gc.properties || true - name: Cleanup + if: always() run: | - rm secring.gpg + rm -f secring.gpg || true diff --git a/build.gradle.kts b/build.gradle.kts index 3be5d7d..5651590 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,8 +11,10 @@ import kotlin.reflect.KProperty buildscript { repositories { + jcenter() mavenCentral() mavenLocal() + maven(url = "https://repo.gradle.org/artifactory/jcenter-backup/") } dependencies { classpath(Libs.gradle_release_plugin) @@ -78,6 +80,7 @@ subprojects { jcenter() mavenCentral() mavenLocal() + maven(url = "https://repo.gradle.org/artifactory/jcenter-backup/") } val sourcesJar by tasks.creating(Jar::class) { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 876c922..77b4e1f 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,5 +3,8 @@ plugins { } repositories { + jcenter() mavenCentral() + mavenLocal() + maven(url = "https://repo.gradle.org/artifactory/jcenter-backup/") } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 241f2e4..a81df55 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -19,10 +19,7 @@ object Vers { const val dynamic_property = "1.1.9" const val jfix_stdlib = "2.0.2" const val corounit = "1.1.1" - const val koin = "2.0.1" - - const val aspectj = "1.9.5" - const val freefair_aspectj = "5.0.1" + const val koin = "2.2.3" } object Libs { @@ -46,14 +43,6 @@ object Libs { const val groovy = "org.codehaus.groovy:groovy:3.0.1" const val groovy_xml = "org.codehaus.groovy:groovy-xml:3.0.1" - const val dynamic_property_api = "ru.fix:dynamic-property-api:${Vers.dynamic_property}" - - const val dynamic_property_std_source = "ru.fix:dynamic-property-std-source:${Vers.dynamic_property}" - const val dynamic_property_jackson = "ru.fix:dynamic-property-jackson:${Vers.dynamic_property}" - const val dynamic_property_spring = "ru.fix:dynamic-property-spring:${Vers.dynamic_property}" - const val jfix_stdlib_files = "ru.fix:jfix-stdlib-files:${Vers.jfix_stdlib}" - - const val jfix_stdlib_concurrency = "ru.fix:jfix-stdlib-concurrency:${Vers.jfix_stdlib}" const val jfix_corounit_engine = "ru.fix:corounit-engine:${Vers.corounit}" const val jfix_corounit_allure = "ru.fix:corounit-allure:${Vers.corounit}" @@ -86,7 +75,7 @@ object Libs { const val wiremock = "com.github.tomakehurst:wiremock:2.26.1" const val jfix_stdlib_socket = "ru.fix:jfix-stdlib-socket:${Vers.jfix_stdlib}" - const val koin = "org.koin:koin-core:${Vers.koin}" + const val koin = "io.insert-koin:koin-core:${Vers.koin}" } enum class Projs { diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/CookieRestTest.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/CookieRestTest.kt index da4e82f..68dd80a 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/CookieRestTest.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/CookieRestTest.kt @@ -3,8 +3,8 @@ package ru.fix.kbdd.example.cases.documentation import io.qameta.allure.Epic import io.qameta.allure.Feature import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.corounit.allure.invoke import ru.fix.kbdd.asserts.* diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/IntroductionTest.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/IntroductionTest.kt index 1ad0f45..31008bf 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/IntroductionTest.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/IntroductionTest.kt @@ -3,8 +3,8 @@ package ru.fix.kbdd.example.cases.documentation import io.qameta.allure.Description import io.qameta.allure.Epic import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.corounit.allure.invoke import ru.fix.kbdd.asserts.* diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/JsonRestTest.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/JsonRestTest.kt index 6507932..471420f 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/JsonRestTest.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/JsonRestTest.kt @@ -3,8 +3,8 @@ package ru.fix.kbdd.example.cases.documentation import io.qameta.allure.Epic import io.qameta.allure.Feature import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.kbdd.asserts.isContains import ru.fix.kbdd.asserts.isEquals diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/NavigationTest.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/NavigationTest.kt index 9c38dd1..49387f5 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/NavigationTest.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/NavigationTest.kt @@ -4,8 +4,8 @@ import io.qameta.allure.Description import io.qameta.allure.Epic import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.corounit.allure.invoke import ru.fix.kbdd.asserts.* diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ParameterizedTest.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ParameterizedTest.kt index ca8de2e..5876326 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ParameterizedTest.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ParameterizedTest.kt @@ -3,8 +3,8 @@ package ru.fix.kbdd.example.cases.documentation import io.qameta.allure.Epic import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.corounit.allure.parameterized import ru.fix.corounit.allure.row diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/RedirectRestTest.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/RedirectRestTest.kt index d211d04..54365fa 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/RedirectRestTest.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/RedirectRestTest.kt @@ -4,8 +4,8 @@ import com.google.common.net.HttpHeaders import io.qameta.allure.Epic import io.qameta.allure.Feature import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.corounit.allure.invoke import ru.fix.kbdd.asserts.get diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ResponseAsserts.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ResponseAsserts.kt index a96a0dc..714d93d 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ResponseAsserts.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ResponseAsserts.kt @@ -4,8 +4,8 @@ import io.qameta.allure.Description import io.qameta.allure.Epic import io.qameta.allure.Feature import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.corounit.allure.invoke import ru.fix.kbdd.asserts.* diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ResponseCustomAsserts.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ResponseCustomAsserts.kt index 363865a..46af657 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ResponseCustomAsserts.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/ResponseCustomAsserts.kt @@ -5,8 +5,8 @@ import io.qameta.allure.Epic import io.qameta.allure.Feature import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.corounit.allure.invoke import ru.fix.kbdd.asserts.* diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/XmlRestTest.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/XmlRestTest.kt index 58d4bc6..a19fd3b 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/XmlRestTest.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/documentation/XmlRestTest.kt @@ -5,8 +5,8 @@ import io.kotest.matchers.shouldBe import io.qameta.allure.Epic import io.qameta.allure.Feature import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.kbdd.asserts.* import ru.fix.kbdd.example.MockServer diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/example/AirportBookingTest.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/example/AirportBookingTest.kt index 25f556d..f1cd497 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/example/AirportBookingTest.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/cases/example/AirportBookingTest.kt @@ -5,8 +5,8 @@ import io.qameta.allure.Epic import io.qameta.allure.Feature import io.qameta.allure.Story import org.junit.jupiter.api.Test -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Package import ru.fix.corounit.allure.invoke import ru.fix.kbdd.asserts.* diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/config/Settings.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/config/Settings.kt index 2a972e0..e0ecfa6 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/config/Settings.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/config/Settings.kt @@ -1,6 +1,6 @@ package ru.fix.kbdd.example.config -import org.koin.core.KoinComponent +import org.koin.core.component.KoinComponent class Settings : KoinComponent { diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/steps/AirportSteps.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/steps/AirportSteps.kt index 87b0fe5..0f42bf0 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/steps/AirportSteps.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/steps/AirportSteps.kt @@ -1,7 +1,7 @@ package ru.fix.kbdd.example.steps -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.kbdd.asserts.* import ru.fix.kbdd.example.config.Settings import ru.fix.kbdd.rest.Rest diff --git a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/steps/BillingSteps.kt b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/steps/BillingSteps.kt index 46f1529..11510ab 100644 --- a/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/steps/BillingSteps.kt +++ b/kbdd-example/src/test/kotlin/ru/fix/kbdd/example/steps/BillingSteps.kt @@ -1,7 +1,7 @@ package ru.fix.kbdd.example.steps -import org.koin.core.KoinComponent -import org.koin.core.inject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import ru.fix.corounit.allure.Step import ru.fix.kbdd.example.config.Settings import ru.fix.kbdd.rest.Rest diff --git a/kbdd/src/main/kotlin/ru/fix/kbdd/rest/Rest.kt b/kbdd/src/main/kotlin/ru/fix/kbdd/rest/Rest.kt index 280e0ce..032fcef 100644 --- a/kbdd/src/main/kotlin/ru/fix/kbdd/rest/Rest.kt +++ b/kbdd/src/main/kotlin/ru/fix/kbdd/rest/Rest.kt @@ -13,7 +13,7 @@ import io.restassured.path.json.config.JsonPathConfig import io.restassured.response.Response import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.withContext +import kotlinx.coroutines.runInterruptible import mu.KotlinLogging import ru.fix.corounit.allure.AllureStep import ru.fix.kbdd.asserts.AlluredKPath @@ -31,22 +31,23 @@ private val log = KotlinLogging.logger { } object Rest { private val defaultMapper = jacksonObjectMapper() private val doNotSendNullsMapper = defaultMapper.copy() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) private fun selectMapper(sendNulls: Boolean = true) = - if (sendNulls) { - defaultMapper - } else { - doNotSendNullsMapper - } + if (sendNulls) { + defaultMapper + } else { + doNotSendNullsMapper + } private val lastResponse = ThreadLocal() var threadPoolSize = 10 + var restAssuredConfigCustomizer: RestAssuredConfig.() -> RestAssuredConfig = { this } private val dispatcher by lazy { - Executors.newFixedThreadPool(10).asCoroutineDispatcher() + + Executors.newFixedThreadPool(threadPoolSize).asCoroutineDispatcher() + CoroutineExceptionHandler { _, thr -> log.error(thr) {} } } @@ -60,86 +61,88 @@ object Rest { val dsl = RequestDsl().apply(request) val config = RestAssuredConfig.config() - .encoderConfig( - EncoderConfig.encoderConfig().defaultContentCharset(Charsets.UTF_8) - .defaultCharsetForContentType(Charsets.UTF_8, ContentType.JSON) - ) - .decoderConfig( - DecoderConfig.decoderConfig().defaultContentCharset(Charsets.UTF_8) - .defaultCharsetForContentType(Charsets.UTF_8, ContentType.JSON) - ) - .jsonConfig( - // Default value FLOAT_AND_DOUBLE results in rounded fractional values. - // Conversion happens in ConfigurableJsonSlurper. If value fits into Float it converts it by - // calling BigDecimal.floatValue(). - // From floatValue() javadoc: "Note that even when the return - // value is finite, this conversion can lose information about the precision". - JsonConfig.jsonConfig().numberReturnType(JsonPathConfig.NumberReturnType.DOUBLE) - ) - .let { c -> - val followRedirects = dsl.followRedirects - ?: return@let c - - c.redirect(c.redirectConfig.followRedirects(followRedirects)) - } + .encoderConfig( + EncoderConfig.encoderConfig() + .defaultContentCharset(Charsets.UTF_8) + .defaultCharsetForContentType(Charsets.UTF_8, ContentType.JSON) + ) + .decoderConfig( + DecoderConfig.decoderConfig() + .defaultContentCharset(Charsets.UTF_8) + .defaultCharsetForContentType(Charsets.UTF_8, ContentType.JSON) + ) + .jsonConfig( + // Default value FLOAT_AND_DOUBLE results in rounded fractional values. + // Conversion happens in ConfigurableJsonSlurper. If value fits into Float it converts it by + // calling BigDecimal.floatValue(). + // From floatValue() javadoc: "Note that even when the return + // value is finite, this conversion can lose information about the precision". + JsonConfig.jsonConfig().numberReturnType(JsonPathConfig.NumberReturnType.DOUBLE) + ) + .let { config -> + val followRedirects = dsl.followRedirects + ?: return@let config + + config.redirect(config.redirectConfig.followRedirects(followRedirects)) + }.restAssuredConfigCustomizer() val allureStep = AllureStep.fromCurrentCoroutineContext() val spec = given() - .filter(HttpAllureAttachmentFilter(allureStep)) - .run { - when { - dsl.formParams != null -> contentType(ContentType.URLENC) - dsl.bodyJsonDsl != null -> contentType(ContentType.JSON) - dsl.bodyString != null -> contentType(ContentType.JSON) - dsl.bodyXml != null -> contentType(ContentType.XML) - else -> this - } - } - .config(config) - .run { - dsl.headers?.let { headers(it) } ?: this - } - .run { - dsl.baseUrl?.let { baseUri(it) } ?: this + .filter(HttpAllureAttachmentFilter(allureStep)) + .run { + when { + dsl.formParams != null -> contentType(ContentType.URLENC) + dsl.bodyJsonDsl != null -> contentType(ContentType.JSON) + dsl.bodyString != null -> contentType(ContentType.JSON) + dsl.bodyXml != null -> contentType(ContentType.XML) + else -> this } - .run { - when { - dsl.bodyJsonDsl != null -> { - val selectedMapper = selectMapper(dsl.bodyJsonSendNulls) - val objectNode = selectedMapper.json(dsl.bodyJsonDsl!!) - if (!dsl.bodyJsonSendNulls) { - removeNullFiledsInObjectNodes(listOf(objectNode)) - } - val content = selectedMapper.writeValueAsString(objectNode) - body(content) + } + .config(config) + .run { + dsl.headers?.let { headers(it) } ?: this + } + .run { + dsl.baseUrl?.let { baseUri(it) } ?: this + } + .run { + when { + dsl.bodyJsonDsl != null -> { + val selectedMapper = selectMapper(dsl.bodyJsonSendNulls) + val objectNode = selectedMapper.json(dsl.bodyJsonDsl!!) + if (!dsl.bodyJsonSendNulls) { + removeNullFiledsInObjectNodes(listOf(objectNode)) } + val content = selectedMapper.writeValueAsString(objectNode) + body(content) + } - dsl.bodyString != null -> - body(dsl.bodyString!!) + dsl.bodyString != null -> + body(dsl.bodyString!!) - dsl.bodyXml != null -> - body(dsl.bodyXml!!) + dsl.bodyXml != null -> + body(dsl.bodyXml!!) - else -> this - } - } - .run { - dsl.formParams?.let { formParams(it) } ?: this - } - .run { - dsl.queryParams?.let { queryParams(it) } ?: this - } - .run { - dsl.filename?.let { name -> - dsl.fileContent?.let { content -> - multiPart("file", name, content) - } - } ?: this + else -> this } + } + .run { + dsl.formParams?.let { formParams(it) } ?: this + } + .run { + dsl.queryParams?.let { queryParams(it) } ?: this + } + .run { + dsl.filename?.let { name -> + dsl.fileContent?.let { content -> + multiPart("file", name, content) + } + } ?: this + } - val response = withContext(dispatcher) { + val response = runInterruptible(dispatcher) { try { when { dsl.post != null -> spec.post(dsl.post) @@ -147,12 +150,15 @@ object Rest { dsl.delete != null -> spec.delete(dsl.delete) dsl.put != null -> spec.put(dsl.put) else -> throw IllegalArgumentException( - "Neither post, get, delete or put method was declared in request") + "Neither post, get, delete or put method was declared in request" + ) } } catch (exc: Exception) { - throw RuntimeException("Failed to execute request with" + - " baseUrl: ${dsl.baseUrl}" + - ", path: ${dsl.post ?: dsl.get ?: dsl.delete ?: dsl.put}", exc) + throw RuntimeException( + "Failed to execute request with" + + " baseUrl: ${dsl.baseUrl}" + + ", path: ${dsl.post ?: dsl.get ?: dsl.delete ?: dsl.put}", exc + ) } } @@ -284,7 +290,7 @@ object Rest { */ fun json(json: Json.() -> Unit): ObjectNode = defaultMapper.json(json) - private suspend fun rawResponse() = lastResponse.get() ?: throw IllegalStateException("Previous response not found") + private fun rawResponse() = lastResponse.get() ?: throw IllegalStateException("Previous response not found") /** * provide access to response status code @@ -292,10 +298,10 @@ object Rest { suspend fun statusCode(): Checkable { val response = rawResponse() return AlluredKPath( - parentStep = AllureStep.fromCurrentCoroutineContext(), - node = response.statusCode, - mode = KPath.Mode.IMMEDIATE_ASSERT, - path = "statusCode()" + parentStep = AllureStep.fromCurrentCoroutineContext(), + node = response.statusCode, + mode = KPath.Mode.IMMEDIATE_ASSERT, + path = "statusCode()" ) } @@ -305,10 +311,10 @@ object Rest { suspend fun statusLine(): Checkable { val response = rawResponse() return AlluredKPath( - parentStep = AllureStep.fromCurrentCoroutineContext(), - node = response.statusLine, - mode = KPath.Mode.IMMEDIATE_ASSERT, - path = "statusLine()" + parentStep = AllureStep.fromCurrentCoroutineContext(), + node = response.statusLine, + mode = KPath.Mode.IMMEDIATE_ASSERT, + path = "statusLine()" ) } @@ -318,10 +324,10 @@ object Rest { suspend fun bodyString(): Checkable { val response = rawResponse() return AlluredKPath( - parentStep = AllureStep.fromCurrentCoroutineContext(), - node = response.body().asString(), - mode = KPath.Mode.IMMEDIATE_ASSERT, - path = "bodyString()" + parentStep = AllureStep.fromCurrentCoroutineContext(), + node = response.body().asString(), + mode = KPath.Mode.IMMEDIATE_ASSERT, + path = "bodyString()" ) } @@ -331,46 +337,46 @@ object Rest { suspend fun bodyJson(): Explorable { val response = rawResponse() return AlluredKPath( - parentStep = AllureStep.fromCurrentCoroutineContext(), - node = response.jsonPath().get()!!, - mode = KPath.Mode.IMMEDIATE_ASSERT, - path = "bodyJson()" + parentStep = AllureStep.fromCurrentCoroutineContext(), + node = response.jsonPath().get()!!, + mode = KPath.Mode.IMMEDIATE_ASSERT, + path = "bodyJson()" ) } suspend fun bodyXml(): Explorable { val response = rawResponse() return AlluredKPath( - parentStep = AllureStep.fromCurrentCoroutineContext(), - node = response.xmlPath(), - mode = KPath.Mode.IMMEDIATE_ASSERT, - path = "bodyXml()" + parentStep = AllureStep.fromCurrentCoroutineContext(), + node = response.xmlPath(), + mode = KPath.Mode.IMMEDIATE_ASSERT, + path = "bodyXml()" ) } suspend fun cookie(): Explorable { val response = rawResponse() return AlluredKPath( - parentStep = AllureStep.fromCurrentCoroutineContext(), - node = response.cookies, - mode = KPath.Mode.IMMEDIATE_ASSERT, - path = "cookie()" + parentStep = AllureStep.fromCurrentCoroutineContext(), + node = response.cookies, + mode = KPath.Mode.IMMEDIATE_ASSERT, + path = "cookie()" ) } suspend fun headers(): Explorable { val response = rawResponse() return AlluredKPath( - parentStep = AllureStep.fromCurrentCoroutineContext(), - node = response.headers.groupBy { - it.name - }.mapValues { - it.value.joinToString { header -> - header.value - } - }, - mode = KPath.Mode.IMMEDIATE_ASSERT, - path = "headers()" + parentStep = AllureStep.fromCurrentCoroutineContext(), + node = response.headers.groupBy { + it.name + }.mapValues { + it.value.joinToString { header -> + header.value + } + }, + mode = KPath.Mode.IMMEDIATE_ASSERT, + path = "headers()" ) } diff --git a/kbdd/src/test/kotlin/ru/fix/kbdd/rest/RestTest.kt b/kbdd/src/test/kotlin/ru/fix/kbdd/rest/RestTest.kt index 872a829..5a52ebf 100644 --- a/kbdd/src/test/kotlin/ru/fix/kbdd/rest/RestTest.kt +++ b/kbdd/src/test/kotlin/ru/fix/kbdd/rest/RestTest.kt @@ -2,8 +2,15 @@ package ru.fix.kbdd.rest import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.WireMock.* +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.assertions.throwables.shouldThrowAny import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.throwable.shouldHaveCause +import io.kotest.matchers.throwable.shouldHaveCauseInstanceOf +import io.kotest.matchers.throwable.shouldHaveCauseOfType +import io.restassured.config.HttpClientConfig import mu.KotlinLogging +import org.apache.http.params.CoreConnectionPNames import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.fail @@ -14,6 +21,8 @@ import ru.fix.kbdd.rest.Rest.request import ru.fix.kbdd.rest.Rest.statusCode import ru.fix.kbdd.rest.Rest.statusLine import ru.fix.stdlib.socket.SocketChecker +import java.net.SocketTimeoutException +import java.time.Duration private val log = KotlinLogging.logger { } @@ -21,18 +30,26 @@ private val log = KotlinLogging.logger { } class RestTest { private lateinit var server: WireMockServer + companion object { + val HUGE_DELAY_REST_DELAY: Duration = Duration.ofMinutes(1) + } + suspend fun beforeAll() { server = WireMockServer(SocketChecker.getAvailableRandomPort()) server.start() for (path in listOf( - "/json-post-request", - "/json-post-from-string-request")) { - - server.stubFor(post(urlPathEqualTo(path)) - .willReturn(aResponse() + "/json-post-request", + "/json-post-from-string-request" + )) { + + server.stubFor( + post(urlPathEqualTo(path)) + .willReturn( + aResponse() .withHeader("Content-Type", "application/json") - .withBody("""{ + .withBody( + """{ "data":{ "entries":[ { @@ -45,29 +62,52 @@ class RestTest { }], "result":56 } - }"""))) + }""" + ) + ) + ) } for (path in listOf( - "/post-form-data-request", - "/json-post-without-nulls", - "/json-post-with-nulls", - "/json-post-dto-as-part-of-json-dsl", - "/json-post-dto-object-in-body", - "/json-post-dto-with-nulls", - "/json-post-dsl-dto-with-nulls", - "/json-post-dsl-dto-without-nulls", - "/json-post-dto-without-nulls")) { - - server.stubFor(post(urlPathEqualTo(path)) - .willReturn(aResponse() + "/post-form-data-request", + "/json-post-without-nulls", + "/json-post-with-nulls", + "/json-post-dto-as-part-of-json-dsl", + "/json-post-dto-object-in-body", + "/json-post-dto-with-nulls", + "/json-post-dsl-dto-with-nulls", + "/json-post-dsl-dto-without-nulls", + "/json-post-dto-without-nulls" + )) { + + server.stubFor( + post(urlPathEqualTo(path)) + .willReturn( + aResponse() .withHeader("Content-Type", "application/json") - .withBody("""{ + .withBody( + """{ "status":"success" - }"""))) + }""" + ) + ) + ) } + + server.stubFor( + get(urlPathEqualTo("/get-with-huge-delay")) + .willReturn( + aResponse() + .withFixedDelay(HUGE_DELAY_REST_DELAY.toMillis().toInt()) + .withBody( + """{ + "status":"success" + }""" + ) + ) + ) } suspend fun afterAll() { @@ -89,14 +129,14 @@ class RestTest { body { "data" { "entries" % array( - { - "name" % "one" - "value" % 1 - }, - { - "name" % "two" - "value" % 2 - } + { + "name" % "one" + "value" % 1 + }, + { + "name" % "two" + "value" % 2 + } ) } @@ -113,19 +153,19 @@ class RestTest { }.single()["value"].isEquals(2) server.verify( - postRequestedFor(urlPathEqualTo("/json-post-request")) - .withQueryParam("queryParam10", equalTo("10")) - .withQueryParam("queryParam11", equalTo("11")) - .withHeader("my-header1", equalTo("header-value1")) - .withHeader("my-header2", equalTo("header-value2")) + postRequestedFor(urlPathEqualTo("/json-post-request")) + .withQueryParam("queryParam10", equalTo("10")) + .withQueryParam("queryParam11", equalTo("11")) + .withHeader("my-header1", equalTo("header-value1")) + .withHeader("my-header2", equalTo("header-value2")) ) try { bodyJson()["data"]["entries"] - .first { it["value"].isEquals(2) }["name"] - .isEquals("one") + .first { it["value"].isEquals(2) }["name"] + .isEquals("one") fail("there should be an assertion error, but was none") } catch (err: AssertionError) { err.message.shouldContain("first") @@ -158,13 +198,13 @@ class RestTest { statusLine().isContains("OK") server.verify( - postRequestedFor(urlPathEqualTo("/json-post-from-string-request")) - .withQueryParam("queryParam10", equalTo("10")) - .withQueryParam("queryParam11", equalTo("11")) - .withHeader("my-header1", equalTo("header-value1")) - .withHeader("my-header2", equalTo("header-value2")) - .withHeader("Content-Type", containing("application/json")) - .withRequestBody(equalToJson(json)) + postRequestedFor(urlPathEqualTo("/json-post-from-string-request")) + .withQueryParam("queryParam10", equalTo("10")) + .withQueryParam("queryParam11", equalTo("11")) + .withHeader("my-header1", equalTo("header-value1")) + .withHeader("my-header2", equalTo("header-value2")) + .withHeader("Content-Type", containing("application/json")) + .withRequestBody(equalToJson(json)) ) } @@ -182,10 +222,10 @@ class RestTest { bodyJson()["status"].isEquals("success") server.verify( - postRequestedFor(urlPathEqualTo("/post-form-data-request")) - .withRequestBody(containing("formParam10=10")) - .withRequestBody(containing("formParam11=11")) - .withHeader("my-header", equalTo("header-value")) + postRequestedFor(urlPathEqualTo("/post-form-data-request")) + .withRequestBody(containing("formParam10=10")) + .withRequestBody(containing("formParam11=11")) + .withHeader("my-header", equalTo("header-value")) ) } @@ -208,8 +248,8 @@ class RestTest { } """ server.verify( - postRequestedFor(urlPathEqualTo("/json-post-dto-object-in-body")) - .withRequestBody(equalToJson(json)) + postRequestedFor(urlPathEqualTo("/json-post-dto-object-in-body")) + .withRequestBody(equalToJson(json)) ) } @@ -236,8 +276,8 @@ class RestTest { } """ server.verify( - postRequestedFor(urlPathEqualTo("/json-post-dto-as-part-of-json-dsl")) - .withRequestBody(equalToJson(json)) + postRequestedFor(urlPathEqualTo("/json-post-dto-as-part-of-json-dsl")) + .withRequestBody(equalToJson(json)) ) } @@ -255,13 +295,17 @@ class RestTest { } statusCode().isEquals(200) server.verify( - postRequestedFor(urlPathEqualTo("/json-post-with-nulls")) - .withRequestBody(equalToJson(""" + postRequestedFor(urlPathEqualTo("/json-post-with-nulls")) + .withRequestBody( + equalToJson( + """ { "one": 1, "two": null } - """)) + """ + ) + ) ) } @@ -279,12 +323,16 @@ class RestTest { } statusCode().isEquals(200) server.verify( - postRequestedFor(urlPathEqualTo("/json-post-without-nulls")) - .withRequestBody(equalToJson(""" + postRequestedFor(urlPathEqualTo("/json-post-without-nulls")) + .withRequestBody( + equalToJson( + """ { "one": 1 } - """)) + """ + ) + ) ) } @@ -302,14 +350,18 @@ class RestTest { } statusCode().isEquals(200) server.verify( - postRequestedFor(urlPathEqualTo("/json-post-dto-without-nulls")) - .withRequestBody(equalToJson(""" + postRequestedFor(urlPathEqualTo("/json-post-dto-without-nulls")) + .withRequestBody( + equalToJson( + """ { "myObject": { "foo": "foo" } } - """)) + """ + ) + ) ) } @@ -325,13 +377,17 @@ class RestTest { } statusCode().isEquals(200) server.verify( - postRequestedFor(urlPathEqualTo("/json-post-dto-with-nulls")) - .withRequestBody(equalToJson(""" + postRequestedFor(urlPathEqualTo("/json-post-dto-with-nulls")) + .withRequestBody( + equalToJson( + """ { "foo": "foo", "bar": null } - """)) + """ + ) + ) ) } @@ -351,8 +407,10 @@ class RestTest { } statusCode().isEquals(200) server.verify( - postRequestedFor(urlPathEqualTo("/json-post-dsl-dto-with-nulls")) - .withRequestBody(equalToJson(""" + postRequestedFor(urlPathEqualTo("/json-post-dsl-dto-with-nulls")) + .withRequestBody( + equalToJson( + """ { "one": 1, "two": null, @@ -361,7 +419,9 @@ class RestTest { "bar": null } } - """)) + """ + ) + ) ) } @@ -381,18 +441,24 @@ class RestTest { } statusCode().isEquals(200) server.verify( - postRequestedFor(urlPathEqualTo("/json-post-dsl-dto-without-nulls")) - .withRequestBody(equalToJson(""" + postRequestedFor(urlPathEqualTo("/json-post-dsl-dto-without-nulls")) + .withRequestBody( + equalToJson( + """ { "one": 1, "myObject": { "foo": "foo" } } - """)) + """ + ) + ) ) } + + @Test suspend fun `post multipart data`() { request { @@ -406,18 +472,42 @@ class RestTest { bodyJson()["status"].isEquals("success") server.verify( - postRequestedFor(urlPathEqualTo("/post-form-data-request")) - .withHeader("my-header", equalTo("header-value")) - .withHeader("Content-Type", containing("multipart/form-data")) - .withRequestBodyPart( - aMultipart() - .withHeader("Content-Disposition", containing("form-data")) - .withHeader("Content-Disposition", containing("name=\"file\"")) - .withHeader("Content-Disposition", containing("filename=\"multipart-data-file.json\"")) - .withHeader("Content-Type", equalTo("application/octet-stream")) - .withBody(equalTo("{\n \"parameter\": \"value\"\n}")) - .build() - ) + postRequestedFor(urlPathEqualTo("/post-form-data-request")) + .withHeader("my-header", equalTo("header-value")) + .withHeader("Content-Type", containing("multipart/form-data")) + .withRequestBodyPart( + aMultipart() + .withHeader("Content-Disposition", containing("form-data")) + .withHeader("Content-Disposition", containing("name=\"file\"")) + .withHeader("Content-Disposition", containing("filename=\"multipart-data-file.json\"")) + .withHeader("Content-Type", equalTo("application/octet-stream")) + .withBody(equalTo("{\n \"parameter\": \"value\"\n}")) + .build() + ) ) } + + @Test + suspend fun `rest client timeout works`() { + val restClientTimeout: Int = Duration.ofSeconds(2).toMillis().toInt() + Rest.restAssuredConfigCustomizer = { + httpClient( + HttpClientConfig + .httpClientConfig() + .setParam(CoreConnectionPNames.SO_TIMEOUT, restClientTimeout) + .setParam(CoreConnectionPNames.CONNECTION_TIMEOUT, restClientTimeout) + ) + } + + val thrownError = shouldThrowAny { + request { + baseUri(server.baseUrl()) + get("/get-with-huge-delay") + } + } + log.info("Test thrown error: ", thrownError) + thrownError.shouldHaveCause { + it.shouldHaveCauseInstanceOf() + } + } } \ No newline at end of file