Skip to content

Commit

Permalink
chore(pact-jvm-server): Converted PactSession to Kotlin
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Oct 30, 2024
1 parent d154527 commit 0234216
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 77 deletions.
3 changes: 2 additions & 1 deletion pact-jvm-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ dependencies {
exclude module: 'netty-transport-native-epoll'
}
implementation 'org.apache.commons:commons-io:1.3.2'
implementation 'org.apache.tika:tika-core'
implementation 'org.apache.commons:commons-lang3'
implementation 'org.apache.commons:commons-text'
implementation 'org.apache.tika:tika-core'

testImplementation 'org.apache.groovy:groovy'
testImplementation 'org.apache.groovy:groovy-json'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package au.com.dius.pact.server

import au.com.dius.pact.core.matchers.FullRequestMatch
import au.com.dius.pact.core.matchers.PartialRequestMatch
import au.com.dius.pact.core.matchers.RequestMatching
import au.com.dius.pact.core.matchers.RequestMismatch
import au.com.dius.pact.core.model.IResponse
import au.com.dius.pact.core.model.Interaction
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.Pact
import au.com.dius.pact.core.model.Request
import au.com.dius.pact.core.model.Response
import org.apache.commons.text.StringEscapeUtils

data class PactSession(
val expected: Pact?,
val results: PactSessionResults
) {
fun receiveRequest(req: Request): Pair<IResponse, PactSession> {
val invalidResponse = invalidRequest(req)

return if (expected != null) {
val matcher = RequestMatching(expected)
when (val result = matcher.matchInteraction(req)) {
is FullRequestMatch ->
(result.interaction.asSynchronousRequestResponse()!!.response to recordMatched(result.interaction))

is PartialRequestMatch ->
(invalidResponse to recordAlmostMatched(result))

is RequestMismatch ->
(invalidResponse to recordUnexpected(req))
}
} else {
invalidResponse to this
}
}

fun recordUnexpected(req: Request) = this.copy(results = results.addUnexpected(req))

fun recordAlmostMatched(partial: PartialRequestMatch) = this.copy(results = results.addAlmostMatched(partial))

fun recordMatched(interaction: Interaction) = this.copy(results = results.addMatched(interaction))

fun remainingResults() = if (expected != null)
results.addMissing((expected.interactions - results.matched.toSet()).asIterable())
else results

companion object {
val CrossSiteHeaders = mapOf("Access-Control-Allow-Origin" to listOf("*"))
@JvmStatic
val empty = PactSession(null, PactSessionResults.empty)

@JvmStatic
fun forPact(pact: Pact) = PactSession(pact, PactSessionResults.empty)

@JvmStatic
fun invalidRequest(req: Request): IResponse {
val headers = CrossSiteHeaders + mapOf(
"Content-Type" to listOf("application/json"),
"X-Pact-Unexpected-Request" to listOf("1")
)
val body = "{ \"error\": \"Unexpected request : " + StringEscapeUtils.escapeJson(req.toString()) + "\" }"
return Response(500, headers.toMutableMap(), OptionalBody.body(body.toByteArray()))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package au.com.dius.pact.server
import au.com.dius.pact.core.matchers.PartialRequestMatch
import au.com.dius.pact.core.model.Interaction
import au.com.dius.pact.core.model.Request
import scala.collection.JavaConverters.asJavaIterable

data class PactSessionResults(
val matched: List<Interaction>,
Expand All @@ -13,7 +12,7 @@ data class PactSessionResults(
) {
fun addMatched(inter: Interaction) = copy(matched = listOf(inter) + matched)
fun addUnexpected(request: Request) = copy(unexpected = listOf(request) + unexpected)
fun addMissing(inters: scala.collection.Iterable<Interaction>) = copy(missing = asJavaIterable(inters).toList() + missing)
fun addMissing(inters: Iterable<Interaction>) = copy(missing = inters.toList() + missing)
fun addAlmostMatched(partial: PartialRequestMatch) = copy(almostMatched = listOf(partial) + almostMatched)

fun allMatched(): Boolean = missing.isEmpty() && unexpected.isEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package au.com.dius.pact.server

import java.net.URI
import java.util.zip.GZIPInputStream

import au.com.dius.pact.core.model.{OptionalBody, ContentType, Request, Response}
import au.com.dius.pact.core.model.{ContentType, IResponse, OptionalBody, Request}
import com.typesafe.scalalogging.StrictLogging
import io.netty.handler.codec.http.{HttpResponse => NHttpResponse}
import unfiltered.netty.ReceivedMessage
Expand All @@ -22,7 +21,7 @@ object Conversions extends StrictLogging {
}
}

def pactToUnfilteredResponse(response: Response): ResponseFunction[NHttpResponse] = {
def pactToUnfilteredResponse(response: IResponse): ResponseFunction[NHttpResponse] = {
val headers = response.getHeaders
if (response.getBody.isPresent) {
Status(response.getStatus) ~> Headers(headers) ~> ResponseString(response.getBody.valueAsString)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package au.com.dius.pact.server

import au.com.dius.pact.consumer.model.{MockHttpsKeystoreProviderConfig, MockHttpsProviderConfig, MockProviderConfig}
import au.com.dius.pact.core.model.{PactSpecVersion, Request, Response, Pact => PactModel}
import au.com.dius.pact.core.model.{IResponse, PactSpecVersion, Request, Response, Pact => PactModel}
import com.typesafe.scalalogging.StrictLogging

import scala.util.Try
Expand Down Expand Up @@ -32,7 +32,7 @@ object DefaultMockProvider {

// TODO: eliminate horrid state mutation and synchronisation. Reactive stuff to the rescue?
abstract class StatefulMockProvider extends MockProvider with StrictLogging {
private var sessionVar = PactSession.empty
private var sessionVar = PactSession.getEmpty
private var pactVar: Option[PactModel] = None

private def waitForRequestsToFinish() = Thread.sleep(100)
Expand Down Expand Up @@ -69,9 +69,11 @@ abstract class StatefulMockProvider extends MockProvider with StrictLogging {
}
}

final def handleRequest(req: Request): Response = synchronized {
final def handleRequest(req: Request): IResponse = synchronized {
logger.debug("Received request: " + req)
val (response, newSession) = session.receiveRequest(req)
val result = session.receiveRequest(req)
val response = result.getFirst
val newSession = result.getSecond
logger.debug("Generating response: " + response)
sessionVar = newSession
response
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object RequestRouter {
pact <- oldState.get(k)
} yield pact).headOption

def handlePactRequest(request: Request, oldState: ServerState): Option[Response] =
def handlePactRequest(request: Request, oldState: ServerState): Option[IResponse] =
for {
pact <- matchPath(request, oldState)
} yield pact.handleRequest(request)
Expand All @@ -23,7 +23,7 @@ object RequestRouter {

val EMPTY_MAP: util.Map[String, util.List[String]] = Map[String, util.List[String]]().asJava

def pactDispatch(request: Request, oldState: ServerState): Response =
def pactDispatch(request: Request, oldState: ServerState): IResponse =
handlePactRequest(request, oldState) getOrElse new Response(404, EMPTY_MAP,
OptionalBody.body(state404(request, oldState).getBytes))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package au.com.dius.pact.server

import au.com.dius.pact.core.model.{OptionalBody, Response}
import au.com.dius.pact.core.model.{IResponse, OptionalBody, Response}
import ch.qos.logback.classic.Level
import org.slf4j.{Logger, LoggerFactory}

Expand All @@ -16,7 +16,7 @@ object ListServers {
}
}

case class Result(response: Response, newState: ServerState)
case class Result(response: IResponse, newState: ServerState)

object Server extends App {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package au.com.dius.pact.server
import _root_.unfiltered.netty.{SslEngineProvider, cycle => unettyc}
import _root_.unfiltered.{netty => unetty, request => ureq, response => uresp}
import au.com.dius.pact.consumer.model.MockHttpsKeystoreProviderConfig
import au.com.dius.pact.core.model.{Request, Response}
import au.com.dius.pact.core.model.{IResponse, Request}
import io.netty.channel.ChannelHandler.Sharable
import io.netty.handler.codec.{http => netty}

Expand All @@ -26,7 +26,7 @@ class UnfilteredHttpsKeystoreMockProvider(val config: MockHttpsKeystoreProviderC

def convertRequest(nr: UnfilteredRequest): Request = Conversions.unfilteredRequestToPactRequest(nr)

def convertResponse(response: Response): UnfilteredResponse = Conversions.pactToUnfilteredResponse(response)
def convertResponse(response: IResponse): UnfilteredResponse = Conversions.pactToUnfilteredResponse(response)
}

def start(): Unit = server.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package au.com.dius.pact.server
import _root_.unfiltered.netty.{SslContextProvider, cycle => unettyc}
import _root_.unfiltered.{netty => unetty, request => ureq, response => uresp}
import au.com.dius.pact.consumer.model.MockHttpsProviderConfig
import au.com.dius.pact.core.model.{Request, Response}
import au.com.dius.pact.core.model.{IResponse, Request}
import io.netty.channel.ChannelHandler.Sharable
import io.netty.handler.codec.{http => netty}
import io.netty.handler.ssl.util.SelfSignedCertificate
Expand All @@ -27,7 +27,7 @@ class UnfilteredHttpsMockProvider(val config: MockHttpsProviderConfig) extends S

def convertRequest(nr: UnfilteredRequest): Request = Conversions.unfilteredRequestToPactRequest(nr)

def convertResponse(response: Response): UnfilteredResponse = Conversions.pactToUnfilteredResponse(response)
def convertResponse(response: IResponse): UnfilteredResponse = Conversions.pactToUnfilteredResponse(response)
}

def start(): Unit = server.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package au.com.dius.pact.server
import _root_.unfiltered.netty.{cycle => unettyc}
import _root_.unfiltered.{netty => unetty, request => ureq, response => uresp}
import au.com.dius.pact.consumer.model.MockProviderConfig
import au.com.dius.pact.core.model.{Request, Response}
import au.com.dius.pact.core.model.{IResponse, Request}
import io.netty.channel.ChannelHandler.Sharable
import io.netty.handler.codec.{http => netty}

Expand All @@ -24,7 +24,7 @@ class UnfilteredMockProvider(val config: MockProviderConfig) extends StatefulMoc

def convertRequest(nr: UnfilteredRequest): Request = Conversions.unfilteredRequestToPactRequest(nr)

def convertResponse(response: Response): UnfilteredResponse = Conversions.pactToUnfilteredResponse(response)
def convertResponse(response: IResponse): UnfilteredResponse = Conversions.pactToUnfilteredResponse(response)
}

def start(): Unit = server.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import au.com.dius.pact.core.model.Request
import au.com.dius.pact.core.model.RequestResponseInteraction
import spock.lang.Specification

import static scala.collection.JavaConverters.iterableAsScalaIterable

class PactSessionResultsSpec extends Specification {
def 'empty state'() {
given:
Expand Down Expand Up @@ -75,8 +73,8 @@ class PactSessionResultsSpec extends Specification {
def interaction3 = new RequestResponseInteraction('test3')

when:
state = state.addMissing(iterableAsScalaIterable([interaction1]))
state = state.addMissing(iterableAsScalaIterable([interaction2, interaction3]))
state = state.addMissing([interaction1])
state = state.addMissing([interaction2, interaction3])

then:
!state.allMatched()
Expand Down
Loading

0 comments on commit 0234216

Please sign in to comment.