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

org.http4k.testing.Approver.kt Maven / Gradle / Ivy

There is a newer version: 5.35.1.0
Show newest version
package org.http4k.testing

import com.natpryce.hamkrest.MatchResult
import com.natpryce.hamkrest.MatchResult.Match
import com.natpryce.hamkrest.MatchResult.Mismatch
import com.natpryce.hamkrest.Matcher
import org.http4k.core.ContentType
import org.http4k.core.HttpMessage
import org.http4k.core.Response
import org.http4k.core.Status
import org.http4k.core.with
import org.http4k.lens.Header
import org.junit.jupiter.api.Assertions.assertEquals
import org.opentest4j.AssertionFailedError
import java.io.ByteArrayInputStream
import java.io.InputStream

/**
 * Coordinates the comparison of the content for a test.
 */
interface Approver {

    /**
     * Check the content of the passed message against the previously approved content.
     */
    fun  assertApproved(httpMessage: T)

    fun withNameSuffix(suffix: String): Approver
}

class NamedResourceApprover(
    private val name: String,
    private val approvalContent: ApprovalContent,
    private val approvalSource: ApprovalSource,
    private val transformer: ApprovalTransformer<*> = ApprovalTransformer.StringWithNormalisedLineEndings()
) : Approver {

    override fun withNameSuffix(suffix: String) = NamedResourceApprover(
        name = "$name.$suffix",
        approvalContent = approvalContent,
        approvalSource = approvalSource,
        transformer = transformer,
    )

    override fun  assertApproved(httpMessage: T) {
        val approved = approvalSource.approvedFor(name)
        val actual = approvalSource.actualFor(name)
        val actualBytes = approvalContent(httpMessage).readBytes()

        with(approved.input()) {
            actual.output() // ensure the actual is removed

            when (this) {
                null -> when {
                    actualBytes.isNotEmpty() -> {
                        actual.output().write(actualBytes)
                        throw ApprovalFailed("No approved content found", actual, approved)
                    }

                    else -> {}
                }

                else -> try {
                    assertEquals(transformer(approvalContent(this)), transformer(ByteArrayInputStream(actualBytes)))
                } catch (e: AssertionError) {
                    ByteArrayInputStream(actualBytes).copyTo(actual.output())
                    throw AssertionError(ApprovalFailed("Mismatch", actual, approved).message + "\n" + e.message)
                }
            }
        }
    }
}

class ApprovalFailed(prefix: String, actual: ReadResource, expected: ReadResource) :
    RuntimeException("$prefix. To approve output:\nmv '$actual' '$expected'")

fun Approver.assertApproved(response: Response, expectedStatus: Status) =
    assertApproved(response.apply { assertEquals(expectedStatus, response.status) })

fun Approver.assertApproved(content: String, contentType: ContentType? = null) =
    assertApproved(Response(Status.OK).body(content).with(Header.CONTENT_TYPE of contentType))

fun Approver.assertApproved(content: InputStream, contentType: ContentType? = null) =
    assertApproved(Response(Status.OK).body(content).with(Header.CONTENT_TYPE of contentType))

/**
 * Create a Hamkrest Matcher for this message that can be combined with other Matchers
 */
fun  Approver.hasApprovedContent(): Matcher = object : Matcher {

    override val description = "has previously approved content"

    override fun invoke(actual: T): MatchResult =
        try {
            assertApproved(actual)
            Match
        } catch (e: AssertionFailedError) {
            Mismatch(e.localizedMessage)
        }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy