All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.aws.smithy.kotlin.runtime.smithy.test.HttpResponseTest.kt Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */
package aws.smithy.kotlin.runtime.smithy.test

import aws.smithy.kotlin.runtime.client.ExecutionContext
import aws.smithy.kotlin.runtime.http.Headers
import aws.smithy.kotlin.runtime.http.HttpBody
import aws.smithy.kotlin.runtime.http.HttpStatusCode
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineBase
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.http.response.HttpCall
import aws.smithy.kotlin.runtime.http.response.HttpResponse
import aws.smithy.kotlin.runtime.io.SdkByteReadChannel
import aws.smithy.kotlin.runtime.time.Instant
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.runTest

public typealias HttpResponseTestFn = suspend (expectedResponse: T?, mockEngine: HttpClientEngine) -> Unit

public class ExpectedHttpResponse {
    public var statusCode: HttpStatusCode = HttpStatusCode.OK
    public var headers: Map = mapOf()
    public var body: String? = null
    public var bodyMediaType: String? = null
    public var response: T? = null
}

public class HttpResponseTestBuilder {
    internal var expected: ExpectedHttpResponse = ExpectedHttpResponse()
    internal var testFn: HttpResponseTestFn = { _, _ -> }

    /**
     * Setup the expected HTTP response that the service operation should consume
     */
    public fun expected(block: ExpectedHttpResponse.() -> Unit) {
        expected.apply(block)
    }

    /**
     * Invoke the service operation and make assertions about the result. The [HttpClientEngine] to use
     * for the test is provided as input to the function. The [block] is responsible for setting up the
     * service client with the provided engine, providing the input, and executing the
     * operation.
     */
    public fun test(block: HttpResponseTestFn) {
        testFn = block
    }
}

/**
 * Setup a [Smithy HTTP Response Test](https://awslabs.github.io/smithy/1.0/spec/http-protocol-compliance-tests.html#httpresponsetests)
 *
 * # Example
 * ```
 * fun fooTest() = httpResponseTest {
 *
 *     // setup the expected request that was built
 *     expected {
 *         status = HttpStatusCode.fromValue(200)
 *         headers = mapOf("foo" to "bar")
 *         body = """{"baz": "quux"}"""
 *         bodyMediaType = "application/json"
 *         response = MyOutput {
 *             baz = "quux"
 *         }
 *     }
 *
 *     test { expectedResult, mockEngine ->
 *        val input = MyInput{}
 *        val actualResult = client.myOperation(input)
 *        // compare expected and actual
 *     }
 * }
 * ```
 */
@OptIn(ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class)
public fun  httpResponseTest(block: HttpResponseTestBuilder.() -> Unit): TestResult = runTest {
    val testBuilder = HttpResponseTestBuilder().apply(block)

    // provide the mock engine
    val mockEngine = object : HttpClientEngineBase("smithy-test-resp-mock-engine") {
        override suspend fun roundTrip(context: ExecutionContext, request: HttpRequest): HttpCall {
            val headers = Headers {
                testBuilder.expected.headers.forEach { (key, value) ->
                    append(key, value)
                }
            }

            val body: HttpBody = testBuilder.expected.body?.let {
                // emulate a real response stream which typically can only be consumed once
                // see: https://github.com/awslabs/aws-sdk-kotlin/issues/356
                object : HttpBody.ChannelContent() {
                    private var consumed = false
                    override fun readFrom(): SdkByteReadChannel {
                        val content = if (consumed) ByteArray(0) else it.encodeToByteArray()
                        consumed = true
                        return SdkByteReadChannel(content)
                    }
                }
            } ?: HttpBody.Empty

            val resp = HttpResponse(testBuilder.expected.statusCode, headers, body)
            val now = Instant.now()
            return HttpCall(request, resp, now, now)
        }
    }

    testBuilder.testFn.invoke(testBuilder.expected.response, mockEngine)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy