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

tech.sirwellington.alchemy.http.mock.MockAlchemyHttp.kt Maven / Gradle / Ivy

/*
 * Copyright © 2019. Sir Wellington.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package tech.sirwellington.alchemy.http.mock

import com.google.gson.JsonNull
import junit.framework.Assert.fail
import sir.wellington.alchemy.collections.lists.Lists
import sir.wellington.alchemy.collections.maps.Maps
import tech.sirwellington.alchemy.annotations.access.Internal
import tech.sirwellington.alchemy.annotations.arguments.Required
import tech.sirwellington.alchemy.annotations.designs.StepMachineDesign
import tech.sirwellington.alchemy.annotations.designs.StepMachineDesign.Role.MACHINE
import tech.sirwellington.alchemy.arguments.AlchemyAssertion
import tech.sirwellington.alchemy.arguments.Arguments.checkThat
import tech.sirwellington.alchemy.arguments.assertions.instanceOf
import tech.sirwellington.alchemy.arguments.assertions.nonNullReference
import tech.sirwellington.alchemy.arguments.assertions.notNull
import tech.sirwellington.alchemy.http.AlchemyHttp
import tech.sirwellington.alchemy.http.AlchemyRequestSteps
import tech.sirwellington.alchemy.http.HttpResponse
import tech.sirwellington.alchemy.http.exceptions.AlchemyHttpException
import tech.sirwellington.alchemy.http.mock.MockRequest.Companion.ANY_BODY
import tech.sirwellington.alchemy.http.mock.MockRequest.Companion.NO_BODY
import java.lang.String.format
import java.util.concurrent.Callable

/**
 *
 * @author SirWellington
 */
@Internal
@StepMachineDesign(role = MACHINE)
internal open class MockAlchemyHttp(expectedActions: Map>) : AlchemyHttp
{

    private val expectedActions: MutableMap> = Maps.createSynchronized()

    private val requestsMade = Lists.create()

    override val defaultHeaders: Map
        @Required
        get() = emptyMap()

    init
    {
        checkThat(expectedActions).isA(nonNullReference())
        this.expectedActions.putAll(expectedActions)
    }

    @Required
    override fun usingDefaultHeader(key: String, value: String): AlchemyHttp
    {
        return this
    }

    override fun go(): AlchemyRequestSteps.Step1
    {
        return MockSteps.MockStep1(this)
    }

    @Internal
    @Throws(AlchemyHttpException::class)
    open fun getResponseFor(request: MockRequest): HttpResponse
    {
        checkThat(request).isA(expectedRequest())

        requestsMade.add(request)

        val action = findMatchingActionFor(request)!!

        val response: Any
        try
        {
            response = action.call()
        }
        catch (ex: AlchemyHttpException)
        {
            throw ex
        }
        catch (ex: Exception)
        {
            throw AlchemyHttpException(ex)
        }

        checkThat(response)
                .usingMessage(format("Response Type Wanted: %s but actual: null", HttpResponse::class.java))
                .isA(notNull())
                .usingMessage(format("Response Type Wanted: %s but actual: %s", HttpResponse::class.java, response.javaClass))
                .isA(instanceOf(HttpResponse::class.java))

        return response as HttpResponse
    }

    @Internal
    @Throws(AlchemyHttpException::class)
    open fun  getResponseFor(request: MockRequest, expectedClass: Class): T
    {
        checkThat(request, expectedClass)
                .are(notNull())

        requestsMade.add(request)

        checkThat(request)
                .usingMessage("Unexpected Request: " + request)
                .isA(expectedRequest())

        val operation = findMatchingActionFor(request)

        val responseObject: Any
        try
        {
            responseObject = operation!!.call()
        }
        catch (ex: AlchemyHttpException)
        {
            throw ex
        }
        catch (ex: Exception)
        {
            throw AlchemyHttpException(ex)
        }


        checkThat(responseObject)
                .usingMessage(format("Response Type Wanted: %s but actual: %s", responseObject.javaClass, expectedClass))
                .isA(instanceOf(expectedClass))

        return responseObject as T

    }

    @Internal
    open fun verifyAllRequestsMade()
    {
        expected@ for (expectedRequest in expectedActions.keys)
        {
            made@ for (requestMade in requestsMade)
            {
                if (requestsMatch(expectedRequest, requestMade))
                {
                    continue@expected
                }
            }

            //Reaching here means no match was found
            fail("Request never made: " + expectedRequest)
        }
    }

    private fun expectedRequest(): AlchemyAssertion
    {
        return AlchemyAssertion { request ->

            checkThat(request).isA(nonNullReference())

            val action = findMatchingActionFor(request)
            checkThat(action)
                    .usingMessage("request was not expected: [$request]")
                    .isA(nonNullReference())
        }
    }

    private fun findMatchingActionFor(request: MockRequest): Callable<*>?
    {
        val foundInMap = expectedActions[request]

        if (foundInMap != null)
        {
            return foundInMap
        }

        for (element in expectedActions.keys)
        {
            if (requestsMatch(element, request))
            {
                return expectedActions[element]
            }
        }

        return null
    }

    private fun requestsMatch(expected: MockRequest, actual: MockRequest): Boolean
    {

        if (expected.method != actual.method)
        {
            return false
        }

        if (expected.queryParams != actual.queryParams)
        {
            return false
        }

        //If the URL is null, this means any
        if (expected.url != MockRequest.ANY_URL)
        {
            if (expected.url.toString() != actual.url.toString())
            {
                return false
            }
        }

        //Now comparing bodies

        //Any body matches to anything
        if (expected.body === ANY_BODY)
        {
            return true
        }

        if (expected.body === NO_BODY)
        {
            return actual.body == null ||
                   actual.body === NO_BODY ||
                   actual.body === JsonNull.INSTANCE
        }

        //Try a shallow equality first
        if (expected.body === actual.body)
        {
            return true
        }

        //Then fallback to equals() if all else fails
        return expected.body == actual.body

    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy