Skip to content
Permalink
Browse files

Test WebActionBinding

  • Loading branch information...
swankjesse committed Jun 6, 2019
1 parent 4a3fb65 commit b97fab9dad5bc200c3b4ad65a4ff2db949dc732f
@@ -56,7 +56,7 @@ internal class BoundAction<A : WebAction>(
url: HttpUrl
): BoundActionMatch? {
// Confirm the path and method matches
val pathMatcher = pathMatcher(url) ?: return null
val pathMatcher = pathPattern.matcher(url) ?: return null
if (requestDispatchMechanism != dispatchMechanism) return null

// Confirm the request content type matches the types we accept, and pick the most specific
@@ -120,12 +120,6 @@ internal class BoundAction<A : WebAction>(
return chain.proceed(chain.args) as WebSocketListener
}

/** Returns a Matcher if requestUrl can be matched, else null */
private fun pathMatcher(requestUrl: HttpUrl): Matcher? {
val matcher = pathPattern.regex.matcher(requestUrl.encodedPath())
return if (matcher.matches()) matcher else null
}

internal val metadata: WebActionMetadata by lazy {
WebActionMetadata(
name = action.name,
@@ -44,7 +44,7 @@ import java.util.regex.Matcher
* parameters, reading the request body, writing the response body, or taking the return value.
*/
internal interface FeatureBinding {
fun bind(action: Subject)
fun bind(subject: Subject)

interface Subject {
val webAction: WebAction
@@ -53,7 +53,7 @@ internal interface FeatureBinding {
fun setParameter(index: Int, value: Any?)
fun takeRequestBody(): BufferedSource
fun takeResponseBody(): BufferedSink
fun takeResponse(): Any?
fun takeReturnValue(): Any?
}

interface Factory {
@@ -1,5 +1,7 @@
package misk.web

import okhttp3.HttpUrl
import java.util.regex.Matcher
import java.util.regex.Pattern

/**
@@ -17,6 +19,12 @@ class PathPattern(
val matchesWildcardPath: Boolean
) : Comparable<PathPattern> {

/** Returns a Matcher if requestUrl can be matched, else null */
fun matcher(requestUrl: HttpUrl): Matcher? {
val matcher = regex.matcher(requestUrl.encodedPath())
return if (matcher.matches()) matcher else null
}

override fun hashCode(): Int = pattern.hashCode()

override fun equals(other: Any?): Boolean = other is PathPattern && other.pattern == pattern
@@ -81,7 +81,7 @@ internal class WebActionBinding @Inject constructor(
return httpCall.takeResponseBody()!!
}

override fun takeResponse(): Any? {
override fun takeReturnValue(): Any? {
require(current == returnValueClaimer) { "return value not claimed by $current" }
return returnValue
}
@@ -93,7 +93,7 @@ internal class WebActionBinding @Inject constructor(
) : FeatureBinding.Claimer {
/** Claims are taken by this placeholder until we get the actual FeatureBinding. */
private object Placeholder : FeatureBinding {
override fun bind(action: FeatureBinding.Subject) = throw AssertionError()
override fun bind(subject: FeatureBinding.Subject) = throw AssertionError()
}

/** Claims by who made them. */
@@ -130,26 +130,26 @@ internal class WebActionBinding @Inject constructor(
/** Lock in the claims of a single binding. */
internal fun commitClaims(factory: FeatureBinding.Factory, binding: FeatureBinding?) {
if (requestBody == Placeholder) {
check(binding != null) { "$factory returned null after making a claim " }
check(binding != null) { "$factory returned null after making a claim" }
requestBody = binding
}

var claimedParameter = false
for (i in 0 until parameters.size) {
if (parameters[i] == Placeholder) {
check(binding != null) { "$factory returned null after making a claim " }
check(binding != null) { "$factory returned null after making a claim" }
parameters[i] = binding
claimedParameter = true
}
}

if (responseBody == Placeholder) {
check(binding != null) { "$factory returned null after making a claim " }
check(binding != null) { "$factory returned null after making a claim" }
responseBody = binding
}

if (returnValue == Placeholder) {
check(binding != null) { "$factory returned null after making a claim " }
check(binding != null) { "$factory returned null after making a claim" }
check(!claimedParameter) { "$factory claimed a parameter and the return value of $action" }
returnValue = binding
}
@@ -0,0 +1,180 @@
package misk.web

import misk.Action
import misk.asAction
import misk.web.actions.WebAction
import okio.BufferedSink
import okio.BufferedSource
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.test.assertFailsWith

internal class WebActionBindingTest {
private val defaultFactory = FakeFeatureBindingFactory()
private val requestBodyFactory = FakeFeatureBindingFactory(claimRequestBody = true)
private val responseBodyFactory = FakeFeatureBindingFactory(claimResponseBody = true)
private val returnValueFactory = FakeFeatureBindingFactory(claimReturnValue = true)
private val parametersFactory = FakeFeatureBindingFactory(
claimParameterValues = mutableMapOf(0 to "zero", 1 to "one"))
private val webActionBindingFactory = WebActionBinding.Factory(listOf(
defaultFactory,
requestBodyFactory,
responseBodyFactory,
returnValueFactory,
parametersFactory
))
private val pathPattern = PathPattern.parse("/")
private val fakeApiCallAction = TestAction::fakeApiCall.asAction()
private val voidApiCallAction = TestAction::voidApiCall.asAction()

@Test
internal fun happyPath() {
val binding = webActionBindingFactory.create(
fakeApiCallAction, DispatchMechanism.POST, pathPattern)
val httpCall = FakeHttpCall()
val matcher = pathPattern.matcher(httpCall.url)!!

// Request and response bodies are consumed and parameters are provided.
val parameters = binding.beforeCall(TestAction(), httpCall, matcher)
assertThat(parameters).containsExactly("zero", "one")
assertThat(httpCall.takeRequestBody()).isNull()
assertThat(requestBodyFactory.result?.requestBody).isNotNull()
assertThat(httpCall.takeResponseBody()).isNull()
assertThat(responseBodyFactory.result?.responseBody).isNotNull()

// Return value is consumed.
binding.afterCall(TestAction(), httpCall, matcher, "hello")
assertThat(returnValueFactory.result?.returnValue).isEqualTo("hello")
}

@Test
internal fun unclaimedParameter() {
parametersFactory.claimParameterValues.remove(1)

val e = assertFailsWith<IllegalStateException> {
webActionBindingFactory.create(fakeApiCallAction, DispatchMechanism.POST, pathPattern)
}
assertThat(e).hasMessage("$fakeApiCallAction parameter 1 not claimed")
}

@Test
internal fun claimButReturnNull() {
requestBodyFactory.result = null

val e = assertFailsWith<IllegalStateException> {
webActionBindingFactory.create(fakeApiCallAction, DispatchMechanism.POST, pathPattern)
}
assertThat(e).hasMessage("FakeFactory returned null after making a claim")
}

/** Bindings need to run entirely before, or entirely after target actions. */
@Test
internal fun claimParameterAndReturnValue() {
returnValueFactory.claimReturnValue = false
parametersFactory.claimReturnValue = true

val e = assertFailsWith<IllegalStateException> {
webActionBindingFactory.create(fakeApiCallAction, DispatchMechanism.POST, pathPattern)
}
assertThat(e).hasMessage(
"FakeFactory claimed a parameter and the return value of $fakeApiCallAction")
}

@Test
internal fun doubleClaimParameter() {
defaultFactory.claimParameterValues[1] = "coke"
parametersFactory.claimParameterValues[1] = "pepsi"

val e = assertFailsWith<IllegalStateException> {
webActionBindingFactory.create(fakeApiCallAction, DispatchMechanism.POST, pathPattern)
}
assertThat(e).hasMessage("already claimed by ${defaultFactory.result}")
}

@Test
internal fun claimButDoNotSupplyParameter() {
val binding = webActionBindingFactory.create(
fakeApiCallAction, DispatchMechanism.POST, pathPattern)
val httpCall = FakeHttpCall()
val matcher = pathPattern.matcher(httpCall.url)!!

parametersFactory.result!!.claimParameterValues.remove(1)
val parameters = binding.beforeCall(TestAction(), httpCall, matcher)
assertThat(parameters).containsExactly("zero", null)
}

@Test
internal fun claimGetRequestBody() {
val e = assertFailsWith<IllegalStateException> {
webActionBindingFactory.create(fakeApiCallAction, DispatchMechanism.GET, pathPattern)
}
assertThat(e).hasMessage("cannot claim request body of GET")
}

@Test
internal fun claimReturnValueOnActionThatReturnsUnit() {
val e = assertFailsWith<IllegalStateException> {
webActionBindingFactory.create(voidApiCallAction, DispatchMechanism.POST, pathPattern)
}
assertThat(e).hasMessage("cannot claim the return value of $voidApiCallAction which has none")
}

@Suppress("UNUSED_PARAMETER")
class TestAction : WebAction {
fun fakeApiCall(p0: String, p1: String): String = TODO()
fun voidApiCall(p0: String, p1: String): Unit = TODO()
}

internal class FakeFeatureBinding(
var claimRequestBody: Boolean,
var claimParameterValues: MutableMap<Int, Any>,
var claimResponseBody: Boolean,
var claimReturnValue: Boolean
) : FeatureBinding {
var requestBody: BufferedSource? = null
var responseBody: BufferedSink? = null
var returnValue: Any? = null

override fun bind(subject: FeatureBinding.Subject) {
if (claimRequestBody) {
requestBody = subject.takeRequestBody()
}
for ((index, value) in claimParameterValues) {
subject.setParameter(index, value)
}
if (claimResponseBody) {
responseBody = subject.takeResponseBody()
}
if (claimReturnValue) {
returnValue = subject.takeReturnValue()
}
}

override fun toString() = "FakeFeatureBinding"
}

internal class FakeFeatureBindingFactory(
var claimRequestBody: Boolean = false,
var claimParameterValues: MutableMap<Int, Any> = mutableMapOf(),
var claimResponseBody: Boolean = false,
var claimReturnValue: Boolean = false,
var result: FakeFeatureBinding? = FakeFeatureBinding(
claimRequestBody, claimParameterValues, claimResponseBody, claimReturnValue)
) : FeatureBinding.Factory {
override fun create(
action: Action,
pathPattern: PathPattern,
claimer: FeatureBinding.Claimer
): FeatureBinding? {
if (claimRequestBody) claimer.claimRequestBody()
for (index in claimParameterValues.keys) {
claimer.claimParameter(index)
}
if (claimResponseBody) claimer.claimResponseBody()
if (claimReturnValue) claimer.claimReturnValue()
return result
}

override fun toString() = "FakeFactory"
}
}

0 comments on commit b97fab9

Please sign in to comment.
You can’t perform that action at this time.