From 31287a1fa482142e71615d633fffa5deafdeebd9 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Wed, 20 Nov 2024 16:57:38 +1100 Subject: [PATCH] chore(pact-jvm-server): Converted RequestRouter to kotlin --- .../au/com/dius/pact/server/MockProvider.kt | 2 +- .../au/com/dius/pact/server/RequestRouter.kt | 42 +++ .../com/dius/pact/server/RequestRouter.scala | 41 --- .../com/dius/pact/server/PublishSpec.groovy | 3 - .../dius/pact/server/RequestRouterSpec.groovy | 327 ++++++++++++++++++ 5 files changed, 370 insertions(+), 45 deletions(-) create mode 100644 pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/RequestRouter.kt delete mode 100644 pact-jvm-server/src/main/scala/au/com/dius/pact/server/RequestRouter.scala create mode 100644 pact-jvm-server/src/test/groovy/au/com/dius/pact/server/RequestRouterSpec.groovy diff --git a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/MockProvider.kt b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/MockProvider.kt index fb45fc462..1f45eeb1f 100644 --- a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/MockProvider.kt +++ b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/MockProvider.kt @@ -79,7 +79,7 @@ abstract class StatefulMockProvider: MockProvider { } @Synchronized - fun handleRequest(req: Request): IResponse { + open fun handleRequest(req: Request): IResponse { logger.debug { "Received request: $req" } val (response, newSession) = session.receiveRequest(req) logger.debug { "Generating response: $response" } diff --git a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/RequestRouter.kt b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/RequestRouter.kt new file mode 100644 index 000000000..5b529dd3b --- /dev/null +++ b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/RequestRouter.kt @@ -0,0 +1,42 @@ +package au.com.dius.pact.server + +import au.com.dius.pact.core.model.IResponse +import au.com.dius.pact.core.model.OptionalBody +import au.com.dius.pact.core.model.Request +import au.com.dius.pact.core.model.Response +import io.github.oshai.kotlinlogging.KotlinLogging + +private val logger = KotlinLogging.logger {} + +object RequestRouter { + fun matchPath(request: Request, oldState: ServerState): StatefulMockProvider? { + return oldState.state.entries.firstOrNull { request.path.startsWith(it.key) }?.value + } + + fun handlePactRequest(request: Request, oldState: ServerState): IResponse? { + val pact = matchPath(request, oldState) + return pact?.handleRequest(request) + } + + fun state404(request: Request, oldState: ServerState) = + (oldState.state.entries.map { it.toPair() } + ("path" to request.path)).joinToString(",\n") { "${it.first} -> ${it.second}" } + + fun pactDispatch(request: Request, oldState: ServerState) = + handlePactRequest(request, oldState) ?: Response(404, mutableMapOf(), + OptionalBody.body(state404(request, oldState).toByteArray())) + + private val urlPattern = Regex("/(\\w*)\\?{0,1}.*") + + @JvmStatic + fun dispatch(request: Request, oldState: ServerState, config: Config): Result { + val matchResult = urlPattern.find(request.path) + val (action) = matchResult!!.destructured + return when (action) { + "create" -> Create.apply(request, oldState, config) + "complete" -> Complete.apply(request, oldState) + "publish" -> Publish.apply(request, oldState, config) + "" -> ListServers.apply(oldState) + else -> Result(pactDispatch(request, oldState), oldState) + } + } +} diff --git a/pact-jvm-server/src/main/scala/au/com/dius/pact/server/RequestRouter.scala b/pact-jvm-server/src/main/scala/au/com/dius/pact/server/RequestRouter.scala deleted file mode 100644 index 399732a55..000000000 --- a/pact-jvm-server/src/main/scala/au/com/dius/pact/server/RequestRouter.scala +++ /dev/null @@ -1,41 +0,0 @@ -package au.com.dius.pact.server - -import java.util -import au.com.dius.pact.core.model.{Request, Response, _} -import com.typesafe.scalalogging.StrictLogging - -import scala.collection.JavaConverters._ - -object RequestRouter extends StrictLogging { - def matchPath(request: Request, oldState: ServerState): Option[StatefulMockProvider] = - (for { - k <- oldState.getState.asScala.keys if request.getPath.startsWith(k) - pact <- oldState.getState.asScala.get(k) - } yield pact).headOption - - def handlePactRequest(request: Request, oldState: ServerState): Option[IResponse] = - for { - pact <- matchPath(request, oldState) - } yield pact.handleRequest(request) - - def state404(request: Request, oldState: ServerState): String = - (oldState.getState.asScala + ("path" -> request.getPath)).mkString(",\n") - - val EMPTY_MAP: util.Map[String, util.List[String]] = Map[String, util.List[String]]().asJava - - def pactDispatch(request: Request, oldState: ServerState): IResponse = - handlePactRequest(request, oldState) getOrElse new Response(404, EMPTY_MAP, - OptionalBody.body(state404(request, oldState).getBytes)) - - def dispatch(request: Request, oldState: ServerState, config: Config): Result = { - val urlPattern ="/(\\w*)\\?{0,1}.*".r - val urlPattern(action) = request.getPath - action match { - case "create" => Create.apply(request, oldState, config) - case "complete" => Complete.apply(request, oldState) - case "publish" => Publish.apply(request, oldState, config) - case "" => ListServers.apply(oldState) - case _ => new Result(pactDispatch(request, oldState), oldState) - } - } -} diff --git a/pact-jvm-server/src/test/groovy/au/com/dius/pact/server/PublishSpec.groovy b/pact-jvm-server/src/test/groovy/au/com/dius/pact/server/PublishSpec.groovy index 96529b85c..33901b298 100644 --- a/pact-jvm-server/src/test/groovy/au/com/dius/pact/server/PublishSpec.groovy +++ b/pact-jvm-server/src/test/groovy/au/com/dius/pact/server/PublishSpec.groovy @@ -3,9 +3,6 @@ package au.com.dius.pact.server import au.com.dius.pact.core.model.OptionalBody import au.com.dius.pact.core.model.Request import au.com.dius.pact.core.pactbroker.IPactBrokerClient -import scala.Option -import scala.collection.JavaConverters -import scala.collection.immutable.List import spock.lang.Specification import spock.util.environment.RestoreSystemProperties diff --git a/pact-jvm-server/src/test/groovy/au/com/dius/pact/server/RequestRouterSpec.groovy b/pact-jvm-server/src/test/groovy/au/com/dius/pact/server/RequestRouterSpec.groovy new file mode 100644 index 000000000..235a46a88 --- /dev/null +++ b/pact-jvm-server/src/test/groovy/au/com/dius/pact/server/RequestRouterSpec.groovy @@ -0,0 +1,327 @@ +package au.com.dius.pact.server + +import au.com.dius.pact.consumer.model.MockProviderConfig +import au.com.dius.pact.core.model.Consumer +import au.com.dius.pact.core.model.OptionalBody +import au.com.dius.pact.core.model.Provider +import au.com.dius.pact.core.model.Request +import au.com.dius.pact.core.model.RequestResponsePact +import au.com.dius.pact.core.model.Response +import spock.lang.Specification + +class RequestRouterSpec extends Specification { + def 'matchPath with empty state'() { + given: + def request = new Request('GET', '/1234') + def state = new ServerState() + + expect: + RequestRouter.INSTANCE.matchPath(request, state) == null + } + + def 'matchPath with equal state'() { + given: + def request = new Request('GET', '/1234') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/2345': provider2 + ]) + + expect: + RequestRouter.INSTANCE.matchPath(request, state) == provider + } + + def 'matchPath with not matching state'() { + given: + def request = new Request('GET', '/abcd') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/2345': provider2 + ]) + + expect: + RequestRouter.INSTANCE.matchPath(request, state) == null + } + + def 'matchPath with matching state'() { + given: + def request = new Request('GET', '/12345678') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/2345': provider2 + ]) + + expect: + RequestRouter.INSTANCE.matchPath(request, state) == provider + } + + def 'handlePactRequest calls handle request on the matching provider'() { + given: + def request = new Request('GET', '/1234') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/2345': provider2 + ]) + + when: + def response = RequestRouter.INSTANCE.handlePactRequest(request, state) + + then: + 1 * provider.handleRequest(request) >> new Response(222) + 0 * provider2.handleRequest(_) + response.status == 222 + } + + def 'handlePactRequest with no matching provider'() { + given: + def request = new Request('GET', '/abcd') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/2345': provider2 + ]) + + when: + def response = RequestRouter.INSTANCE.handlePactRequest(request, state) + + then: + 0 * provider.handleRequest(_) + 0 * provider2.handleRequest(_) + response == null + } + + def 'pactDispatch returns 404 if no matching provider'() { + given: + def request = new Request('GET', '/abcd') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/2345': provider2 + ]) + + when: + def response = RequestRouter.INSTANCE.pactDispatch(request, state) + + then: + 0 * provider.handleRequest(_) + 0 * provider2.handleRequest(_) + response.status == 404 + } + + def 'dispatch sends / requests to the ListServers controller'() { + given: + def request = new Request('GET', '/') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/2345': provider2 + ]) + def config = new Config() + + when: + def response = RequestRouter.dispatch(request, state, config) + + then: + 0 * provider.handleRequest(_) + 0 * provider2.handleRequest(_) + response.response.status == 200 + response.response.body.valueAsString() == '{"ports": [1234, 2345], "paths": ["/1234", "/2345"]}' + } + + def 'dispatch sends /create requests to the Create controller'() { + given: + def request = new Request('GET', '/create') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/create/other': provider2 + ]) + def config = new Config() + + when: + def response = RequestRouter.dispatch(request, state, config) + + then: + 0 * provider.handleRequest(_) + 0 * provider2.handleRequest(_) + response.response.status == 400 + response.response.body.valueAsString() == '{"error": "please provide state param and path param and pact body"}' + } + + def 'dispatch sends /create/* requests to the Create controller'() { + given: + def request = new Request('GET', '/create/other') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/create/o': provider2 + ]) + def config = new Config() + + when: + def response = RequestRouter.dispatch(request, state, config) + + then: + 0 * provider.handleRequest(_) + 0 * provider2.handleRequest(_) + response.response.status == 400 + response.response.body.valueAsString() == '{"error": "please provide state param and path param and pact body"}' + } + + def 'dispatch sends /complete requests to the Complete controller'() { + given: + def request = new Request('GET', '/complete') + request.body = OptionalBody.body('{"port":"1234"}') + def provider = Mock(StatefulMockProvider) { + getSession() >> PactSession.empty + getPact() >> new RequestResponsePact(new Provider(), new Consumer()) + getConfig() >> new MockProviderConfig() + } + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/complete/other': provider2 + ]) + def config = new Config() + + when: + def response = RequestRouter.dispatch(request, state, config) + + then: + 0 * provider.handleRequest(_) + 1 * provider.stop() + 0 * provider2.handleRequest(_) + response.response.status == 200 + } + + def 'dispatch sends /complete/* requests to the Complete controller'() { + given: + def request = new Request('GET', '/complete/other') + request.body = OptionalBody.body('{"port":"1234"}') + def provider = Mock(StatefulMockProvider) { + getSession() >> PactSession.empty + getPact() >> new RequestResponsePact(new Provider(), new Consumer()) + getConfig() >> new MockProviderConfig() + } + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/complete/o': provider2 + ]) + def config = new Config() + + when: + def response = RequestRouter.dispatch(request, state, config) + + then: + 0 * provider.handleRequest(_) + 1 * provider.stop() + 0 * provider2.handleRequest(_) + response.response.status == 200 + } + + def 'dispatch sends /publish requests to the Publish controller'() { + given: + def request = new Request('GET', '/publish') + request.body = OptionalBody.body('{}') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/publish/other': provider2 + ]) + def config = new Config() + + when: + def response = RequestRouter.dispatch(request, state, config) + + then: + 0 * provider.handleRequest(_) + 0 * provider2.handleRequest(_) + response.response.status == 500 + response.response.body.valueAsString() == '{"error" : "Broker url not correctly configured please run server with -b or --broker \'http://pact-broker.adomain.com\' option" }' + } + + def 'dispatch sends /publish/* requests to the Publish controller'() { + given: + def request = new Request('GET', '/publish/other') + request.body = OptionalBody.body('{}') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/publish/o': provider2 + ]) + def config = new Config() + + when: + def response = RequestRouter.dispatch(request, state, config) + + then: + 0 * provider.handleRequest(_) + 0 * provider2.handleRequest(_) + response.response.status == 500 + response.response.body.valueAsString() == '{"error" : "Broker url not correctly configured please run server with -b or --broker \'http://pact-broker.adomain.com\' option" }' + } + + def 'dispatch sends all other requests to pactDispatch'() { + given: + def request = new Request('GET', '/other') + def provider = Mock(StatefulMockProvider) + def provider2 = Mock(StatefulMockProvider) + def state = new ServerState([ + '1234': provider, + '2345': provider2, + '/1234': provider, + '/publish/o': provider2 + ]) + def config = new Config() + + when: + def response = RequestRouter.dispatch(request, state, config) + + then: + 0 * provider.handleRequest(_) + 0 * provider2.handleRequest(_) + response.response.status == 404 + } +}