Closed phil-rice closed 6 years ago
Hi Phil,
Easy stuff first:
Would it be worth you sharing your original bodged version so I can see what was involved? All that follows otherwise is pure guess work! :-)
Less easy stuff, SSL Context:
First a question: Would it be possible to do the consumer tests in vanilla http and the provider verification using SSL? Might make you're life easier if you can given that you can set the provider state, base url, protocol and port at the time you verify. No worries if not, just asking.
Either way, if I understand your requirements properly, then you'll need to supply an SSL context for Http4s to optionally make use of I think. I'm not fully sure of what you need but I'd guess that:
Consumer For the consumer tests there is actually a way to pass optional config to the test framework that, by default could not set a context but you could override it. See: https://github.com/ITV/scala-pact/blob/a07ada3671785c41afecc5090073cd7df09656df/scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactForger.scala#L40 and https://github.com/ITV/scala-pact/blob/a07ada3671785c41afecc5090073cd7df09656df/scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactForger.scala#L138
You would then need to pass that context all the way down to (yes) every version of Http4s to make use of (probably). I'm afraid there is much duplication down there, it's on my list of things to review ...sometime...
Provider My suspicion is that regardless of which method you're using to do the verification, you'll probably want to somehow add a new option to the pact verify settings (possibly) (https://github.com/ITV/scala-pact/blob/a07ada3671785c41afecc5090073cd7df09656df/scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactVerify.scala#L57) saying to use SSL and then some way to build the context per verification (? if it's global you could just put it right in the settings?) from a memento string in the provider state perhaps.
Please don't forget that there are two ways to verify and your solution will need to be ported to both (but I can help with that).
Show me your original version if you can and we can talk again. Otherwise, if you want to send a PR of a dirty but working version we can go from there if you like?
Cheers,
Dave
Thanks David,
I share your views on the JSON format, although it's interesting to consider raising this as an issue in the coordination group. We won't be the only people with certificate issues, especially as usage grows.
It's not really provider state because it's part of the request.. We actually have to include the certificate when we make the request as well as when we validate. i.e. it's not really 'provider' state at all it's actually consumer state. I started there but couldn't bring myself to do it: it just felt wrong. I've come up with a very simple idea: have a fake header. The fake header has a value which is the lookup into a map. This header is part of the request (good) is in the Json file already (good) and the data it represents is often in the real world put onto headers anyway (i.e. NGINX would rip the data out of the certificate and pass it to the end point as a header. The place in the code that does this is encapsulated in SslContextMap so if we change our mind and decide it's really provider state, it's really simple to sort it out.
The old bodged version is messy and scattered throughout our code. (we didn't really fork it we copied the files we wanted and edited them... as I say it was a bodge). It also didn't modify the json file: we were in charge of both ends. The work is pretty simple I've attached a patch which is WIP. I think it probably works: the compiler says it does. But because it's SSL certificates I'm in the middle of test pain. I just love SSL Engine Errors. The patch base was 'WIP: provider_test example does not work' . I'm not very familiar with making patches so I'm not sure if you apply it to master or to 'add script for release local'
The patch has a default implicit which (I hope: still need to test it) is threaded through the code to the point at which the connections are made. The default implicit says 'there aren't any SSL Contexts'. By setting a header in the request (which is removed from the actual HTTPS call) the pact user can do this. This feels a little clunky so when I've got this working, I might modify the builder to have a 'with ssl context' and only have the json file know about the fake header.
Just as an aside: a separate issue... Our microservices are behind an API gateway. The API gateway is responsible for things like OPTIONS requests, and also modifies the payload a bit... So when we are consuming our microservices as javascript in the browser, the JSON file we produce is guaranteeded to be unverifiable by the microservice. We split it into two using good old AWK JQ SED style of working and end up with two pacts. One for the API gateway, and one for the actual microservice. We've got a solution for this, I just though you'd be interested in the problem
@Summary I'm working on the pull request: Just need to check it actually works.
regards
Phil
On 16 November 2017 at 18:52, Dave Smith notifications@github.com wrote:
Hi Phil,
Easy stuff first:
- Master branch is up to date.
- I believe we shouldn't modify the Pact JSON format because it will be incompatible with other Pact implementations (including the broker).
- I would have thought this would belong in the provider state section, because it's a requirement the provider must satisfy for verification to happen?
Would it be worth you sharing your original bodged version so I can see what was involved? All that follows otherwise is pure guess work! :-)
Less easy stuff, SSL Context:
First a question: Would it be possible to do the consumer tests in vanilla http and the provider verification using SSL? Might make you're life easier if you can given that you can set the provider state, base url, protocol and port at the time you verify. No worries if not, just asking.
Either way, if I understand your requirements properly, then you'll need to supply an SSL context for Http4s to optionally make use of I think. I'm not fully sure of what you need but I'd guess that:
Consumer For the consumer tests there is actually a way to pass optional config to the test framework that, by default could not set a context but you could override it. See: https://github.com/ITV/scala-pact/blob/a07ada3671785c41afecc5090073cd 7df09656df/scalapact-scalatest/src/main/scala/com/ itv/scalapact/ScalaPactForger.scala#L40 and https://github.com/ITV/scala-pact/blob/a07ada3671785c41afecc5090073cd 7df09656df/scalapact-scalatest/src/main/scala/com/ itv/scalapact/ScalaPactForger.scala#L138
You would then need to pass that context all the way down to (yes) every version of Http4s to make use of (probably). I'm afraid there is much duplication down there, it's on my list of things to review ...sometime...
Provider My suspicion is that regardless of which method you're using to do the verification, you'll probably want to somehow add a new option to the pact verify settings (possibly) (https://github.com/ITV/scala-pact/blob/ a07ada3671785c41afecc5090073cd7df09656df/scalapact- scalatest/src/main/scala/com/itv/scalapact/ScalaPactVerify.scala#L57) saying to use SSL and then some way to build the context per verification (? if it's global you could just put it right in the settings?) from a memento string in the provider state perhaps.
Please don't forget that there are two ways to verify and your solution will need to be ported to both (but I can help with that).
Show me your original version if you can and we can talk again. Otherwise, if you want to send a PR of a dirty but working version we can go from there if you like?
Cheers,
Dave
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/ITV/scala-pact/issues/74#issuecomment-345004161, or mute the thread https://github.com/notifications/unsubscribe-auth/AART8hQh-tZ_aKkB1tAazX0o6nRMLOu-ks5s3HZOgaJpZM4QgIYl .
-- Stuff our lawyer makes us say:
--- example/provider/delivered_pacts/Consumer_Provider.json (date 1510221100000) +++ example/provider/delivered_pacts/Consumer_Provider.json (date 1510587421000) @@ -7,25 +7,6 @@ }, "interactions" : [ {
--- example/provider_tests/build.sbt (date 1510221100000) +++ example/provider_tests/build.sbt (date 1510587421000) @@ -8,12 +8,16 @@
libraryDependencies ++= Seq(
--- example/provider_tests/delivered_pacts/Consumer_Provider.json (date 1510221100000) +++ example/provider_tests/delivered_pacts/Consumer_Provider.json (date 1510587421000) @@ -7,25 +7,6 @@ }, "interactions" : [ {
--- example/provider_tests/src/main/scala/com/example/provider/Provider.scala (date 1510221100000) +++ example/provider_tests/src/main/scala/com/example/provider/Provider.scala (date 1510587421000) @@ -1,19 +1,16 @@ package com.example.provider
+import org.http4s.circe. +import org.http4s.dsl.io. import org.http4s.util.CaseInsensitiveString
object Provider {
@@ -23,7 +20,7 @@ val nameHeader = request.headers.get(CaseInsensitiveString("Name")).map(_.value)
(acceptHeader, nameHeader) match {
@@ -40,16 +37,5 @@
}
-object ResultResponseImplicits {
case class ResultResponse(count: Int, results: List[String])
-object TokenResponseImplicits {
--- example/provider_tests/src/main/scala/com/example/provider/Server.scala (date 1510221100000) +++ example/provider_tests/src/main/scala/com/example/provider/Server.scala (date 1510587421000) @@ -1,5 +1,6 @@ package com.example.provider
+import cats.effect.IO import org.http4s.server.blaze.BlazeBuilder import org.http4s.server._
@@ -9,7 +10,7 @@ def main(args: Array[String]): Unit = {
// Here we inject the real functions that do all the work
startServer(BusinessLogic.loadPeople, BusinessLogic.generateToken).awaitShutdown()
startServer(BusinessLogic.loadPeople, BusinessLogic.generateToken) }
// This function allows us to start the service while supplying the dependencies @@ -17,13 +18,14 @@ // this function is called directly, say from our test suite, we can inject // any core logic we like, thus side stepping a few pesky little things... // ...like databases.
def startServer(loadPeopleData: String => List[String], genToken: Int => String): Server = {
BlazeBuilder.bindHttp(8080)
def startServer(loadPeopleData: String => List[String], genToken: Int => String): Server[IO] = {
BlazeBuilder[IO].bindHttp(8080) .mountService(Provider.service(loadPeopleData)(genToken), "/")
.run
}
.start
.unsafeRunSync()
}
def stopServer(server: Server): Unit = {
def stopServer(server: Server[IO]): Unit = { server.shutdown }
--- example/provider_tests/src/test/scala/com/example/provider/VerifyContractsSpec.scala (date 1510221100000) +++ example/provider_tests/src/test/scala/com/example/provider/VerifyContractsSpec.scala (date 1510587421000) @@ -1,14 +1,14 @@ package com.example.provider
import com.itv.scalapact.ScalaPactVerify._
class VerifyContractsSpec extends FunSpec with Matchers with BeforeAndAfterAll {
// Their are almost certainly nicer ways to do this.
var runningService: Option[Server] = None
var runningService: Option[Server[IO]] = None
// Before all the tests are run, we start our service, which is the real service // code, the only difference is that the core business logic has been replaced @@ -27,7 +27,7 @@
// Afterwards we need to remember to shut our service down again. override def afterAll(): Unit = {
runningService.foreach(AlternateStartupApproach.stopServer)
runningService.foreach(p => p.shutdownNow()) }
--- scalapact-core/src/main/scala/com/itv/scalapactcore/verifier/Verifier.scala (date 1508775849000) +++ scalapact-core/src/main/scala/com/itv/scalapactcore/verifier/Verifier.scala (date 1510827601000) @@ -11,7 +11,7 @@
object Verifier {
def verify(loadPactFiles: String => ScalaPactSettings => ConfigAndPacts, pactVerifySettings: PactVerifySettings)(implicit pactReader: IPactReader): ScalaPactSettings => Boolean = arguments => {
def verify(loadPactFiles: String => ScalaPactSettings => ConfigAndPacts, pactVerifySettings: PactVerifySettings)(implicit pactReader: IPactReader, sslContextMap: SslContextMap): ScalaPactSettings => Boolean = arguments => {
val pacts: List[Pact] = if(arguments.localPactFilePath.isDefined) {
println(s"Attempting to use local pact files at: '${arguments.localPactFilePath.getOrElse("
private def doRequest(arguments: ScalaPactSettings, maybeProviderState: Option[ProviderState]): InteractionRequest => Either[String, InteractionResponse] = interactionRequest => {
private def doRequest(arguments: ScalaPactSettings, maybeProviderState: Option[ProviderState])(implicit sslContextMap: SslContextMap): InteractionRequest => Either[String, InteractionResponse] = interactionRequest => { val baseUrl = s"${arguments.giveProtocol}://" + arguments.giveHost + ":" + arguments.givePort.toString val clientTimeout = arguments.giveClientTimeout
@@ -165,7 +165,7 @@
}
private def fetchAndReadPact(address: String)(implicit pactReader: IPactReader, sslContextMap: SslContextMap): Option[Pact] = { println(s"Attempting to fetch pact from pact broker at: $address".white.bold)
--- scalapact-http4s-0-15-0a/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1508775849000) +++ scalapact-http4s-0-15-0a/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1510827601000) @@ -1,5 +1,7 @@ package com.itv.scalapact.shared.http
+import javax.net.ssl.SSLContext + import com.itv.scalapact.shared.{SimpleRequest, SimpleResponse} import org.http4s.{BuildInfo, Response} import org.http4s.client.Client @@ -13,21 +15,22 @@
import HeaderImplicitConversions._
private def blazeClientConfig(clientTimeout: Duration): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
private def blazeClientConfig(clientTimeout: Duration, sslContext: Option[SSLContext]): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
requestTimeout = clientTimeout,
userAgent = Option(User-Agent
(AgentProduct("scala-pact", Option(BuildInfo.version)))),
endpointAuthentication = false,
customExecutor = None
customExecutor = None,
sslContext = sslContext )
private val extractResponse: Response => Task[SimpleResponse] = r => r.bodyAsText.runLog[Task, String].map(_.mkString).map { b => SimpleResponse(r.status.code, r.headers, Some(b)) }
def defaultClient: Client =
buildPooledBlazeHttpClient(1, Duration(1, SECONDS))
buildPooledBlazeHttpClient(1, Duration(1, SECONDS), sslContext=None)
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout))
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration, sslContext: Option[SSLContext]): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout, sslContext ))
--- scalapact-http4s-0-15-0a/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-http4s-0-15-0a/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1510827601000) @@ -28,32 +28,32 @@
val maxTotalConnections: Int = 1
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse] =
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse] =
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Future[InteractionResponse] = doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout)
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse] =
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest).unsafePerformSyncAttempt.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse] =
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Either[Throwable, InteractionResponse] = doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout).unsafePerformSyncAttempt.toEither
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest): Task[SimpleResponse] =
performRequest(simpleRequest, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds))
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Task[SimpleResponse] =
SslContextMap(simpleRequest)(sslContext => simpleRequestWithoutFakeheader => performRequest(simpleRequestWithoutFakeheader, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds, sslContext)))
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration): Task[InteractionResponse] =
performRequest(
SimpleRequest( url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body),
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if(r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
}
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Task[InteractionResponse] =
SslContextMap(SimpleRequest(url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body))(
sslContext => simpleRequestWithoutFakeheader =>
performRequest(simpleRequestWithoutFakeheader,
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout, sslContext)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if (r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
--- scalapact-http4s-0-16-2/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-http4s-0-16-2/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1510827601000) @@ -28,22 +28,22 @@
val maxTotalConnections: Int = 1
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse] =
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse] =
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Future[InteractionResponse] = doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout)
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse] =
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest).attemptRun.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse] =
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Either[Throwable, InteractionResponse] = doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout).attemptRun.toEither
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest): Task[SimpleResponse] =
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Task[SimpleResponse] = performRequest(simpleRequest, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds))
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration): Task[InteractionResponse] =
--- scalapact-http4s-0-16-2a/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1508775849000) +++ scalapact-http4s-0-16-2a/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1510827601000) @@ -1,6 +1,8 @@ package com.itv.scalapact.shared.http
-import com.itv.scalapact.shared.{SimpleRequest, SimpleResponse} +import javax.net.ssl.SSLContext + +import com.itv.scalapact.shared.{SimpleRequest, SimpleResponse, SslContextMap} import org.http4s.{BuildInfo, Response} import org.http4s.client.Client import org.http4s.client.blaze.{BlazeClientConfig, PooledHttp1Client} @@ -13,20 +15,21 @@
import HeaderImplicitConversions._
private def blazeClientConfig(clientTimeout: Duration): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
private def blazeClientConfig(clientTimeout: Duration, sslContext: Option[SSLContext]): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
requestTimeout = clientTimeout,
userAgent = Option(User-Agent
(AgentProduct("scala-pact", Option(BuildInfo.version)))),
checkEndpointIdentification = false
checkEndpointIdentification = false,
sslContext=sslContext )
private val extractResponse: Response => Task[SimpleResponse] = r => r.bodyAsText.runLog[Task, String].map(_.mkString).map { b => SimpleResponse(r.status.code, r.headers, Some(b)) }
def defaultClient: Client =
buildPooledBlazeHttpClient(1, Duration(1, SECONDS))
buildPooledBlazeHttpClient(1, Duration(1, SECONDS), sslContext = None)
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout))
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration,sslContext: Option[SSLContext]): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout, sslContext))
--- scalapact-http4s-0-16-2a/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-http4s-0-16-2a/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1510827601000) @@ -28,32 +28,34 @@
val maxTotalConnections: Int = 1
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse] =
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse] =
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Future[InteractionResponse] = doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout)
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse] =
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest).unsafePerformSyncAttempt.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse] =
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Either[Throwable, InteractionResponse] = doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout).unsafePerformSyncAttempt.toEither
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest): Task[SimpleResponse] =
performRequest(simpleRequest, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds))
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Task[SimpleResponse] =
SslContextMap(simpleRequest)(sslContext => simpleRequestWithoutFakeHeader => performRequest(simpleRequestWithoutFakeHeader, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds, sslContext)))
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration): Task[InteractionResponse] =
performRequest(
SimpleRequest( url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body),
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if(r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Task[InteractionResponse] =
SslContextMap(SimpleRequest(url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body)) {
(sslContext => simpleRequestWithoutFakeHeader => performRequest(
simpleRequestWithoutFakeHeader,
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout, sslContext)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if (r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
}) }
--- scalapact-http4s-0-17-0/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1508775849000) +++ scalapact-http4s-0-17-0/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1510827601000) @@ -1,5 +1,7 @@ package com.itv.scalapact.shared.http
+import javax.net.ssl.SSLContext + import com.itv.scalapact.shared.{SimpleRequest, SimpleResponse} import fs2.{Strategy, Task} import org.http4s.client.Client @@ -16,8 +18,9 @@
private[http] implicit val strategy: Strategy = fs2.Strategy.fromFixedDaemonPool(2, threadName = "strategy")
private def blazeClientConfig(clientTimeout: Duration): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
private def blazeClientConfig(clientTimeout: Duration, sslContext: Option[SSLContext]): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy( requestTimeout = clientTimeout,
sslContext=sslContext,
userAgent = Option(User-Agent
(AgentProduct("scala-pact", Option(BuildInfo.version)))),
checkEndpointIdentification = false
)
@@ -26,16 +29,16 @@
r.bodyAsText.runLog.map(_.mkString).map { b => SimpleResponse(r.status.code, r.headers, Some(b)) }
def defaultClient: Client =
buildPooledBlazeHttpClient(1, Duration(1, SECONDS))
buildPooledBlazeHttpClient(1, Duration(1, SECONDS), sslContext = None)
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout))
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration, sslContext: Option[SSLContext]): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout,sslContext))
val doRequest: (SimpleRequest, Client) => Task[SimpleResponse] = (request, httpClient) => for {
request <- Http4sRequestResponseFactory.buildRequest(request)
request <- Http4sRequestResponseFactory.buildRequest(request) response <- httpClient.fetchSimpleResponse(extractResponse)
_ <- httpClient.shutdown
_ <- httpClient.shutdown } yield response
--- scalapact-http4s-0-17-0/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-http4s-0-17-0/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1510827601000) @@ -31,32 +31,37 @@
private implicit val strategy: Strategy = Http4sClientHelper.strategy
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse] =
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse] =
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Future[InteractionResponse] = doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout)
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse] =
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest).unsafeAttemptRun()
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse] =
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Either[Throwable, InteractionResponse] = doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout).unsafeAttemptRun()
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest): Task[SimpleResponse] =
performRequest(simpleRequest, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds))
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Task[SimpleResponse] =
SslContextMap(simpleRequest)(sslContext => simpleRequestWithoutFakeheader => performRequest(simpleRequestWithoutFakeheader, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds, sslContext)))
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration): Task[InteractionResponse] =
performRequest(
SimpleRequest( url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body),
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if(r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Task[InteractionResponse] =
SslContextMap(SimpleRequest(url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body)) {
sslContext =>
simpleRequestWithoutFakeheader =>
performRequest(
simpleRequestWithoutFakeheader,
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout, sslContext)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if (r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
} }
--- scalapact-sbtplugin/src/main/scala/com/itv/scalapact/plugin/verifier/ScalaPactVerifyCommand.scala (date 1508775849000) +++ scalapact-sbtplugin/src/main/scala/com/itv/scalapact/plugin/verifier/ScalaPactVerifyCommand.scala (date 1510827601000) @@ -3,7 +3,7 @@ import com.itv.scalapact.plugin.ScalaPactPlugin import com.itv.scalapactcore.verifier.ProviderState import com.itv.scalapact.shared.ColourOuput. -import com.itv.scalapact.shared.ScalaPactSettings +import com.itv.scalapact.shared.{ScalaPactSettings, SslContextMap} import com.itv.scalapactcore.common.LocalPactFileLoader import com.itv.scalapactcore.verifier. import sbt._ @@ -55,7 +55,7 @@ .map(t => VersionedConsumer(t._1, t._2)) )
val successfullyVerified = verify(LocalPactFileLoader.loadPactFiles(pactReader)(true), pactVerifySettings)(pactReader)(scalaPactSettings)
val successfullyVerified = verify(LocalPactFileLoader.loadPactFiles(pactReader)(true), pactVerifySettings)(pactReader, new SslContextMap(Map()))(scalaPactSettings)
if (successfullyVerified) sys.exit(0) else sys.exit(1)
@@ -63,8 +63,9 @@
def combineProviderStatesIntoTotalFunction(directPactStates: Seq[(String, String => Boolean)], patternMatchedStates: PartialFunction[String, Boolean]): String => Boolean = { val l = directPactStates
}: PartialFunction[String, Boolean] }
--- scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactForger.scala (date 1508775849000) +++ scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactForger.scala (date 1510827601000) @@ -1,5 +1,7 @@ package com.itv.scalapact
+import com.itv.scalapact.shared.SslContextMap + import scala.language.implicitConversions import scala.util.Properties
@@ -37,7 +39,7 @@ */ def addInteraction(interaction: ScalaPactInteraction): ScalaPactDescription = new ScalaPactDescription(consumer, provider, interactions ++ List(interaction))
def runConsumerTest[A](test: ScalaPactMockConfig => A)(implicit options: ScalaPactOptions): A = {
--- scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactMock.scala (date 1508775849000) +++ scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactMock.scala (date 1510827601000) @@ -14,7 +14,7 @@
object ScalaPactMock {
private def configuredTestRunner[A](pactDescription: ScalaPactDescriptionFinal)(config: ScalaPactMockConfig)(test: => ScalaPactMockConfig => A): A = {
private def configuredTestRunner[A](pactDescription: ScalaPactDescriptionFinal)(config: ScalaPactMockConfig)(test: => ScalaPactMockConfig => A)(implicit sslContextMap: SslContextMap): A = {
if(pactDescription.options.writePactFiles) { ScalaPactContractWriter.writePactContracts(config.outputPath)(pactWriter)(pactDescription) @@ -54,7 +54,7 @@ else port }
def runConsumerIntegrationTest[A](strict: Boolean)(pactDescription: ScalaPactDescriptionFinal)(test: ScalaPactMockConfig => A): A = {
def runConsumerIntegrationTest[A](strict: Boolean)(pactDescription: ScalaPactDescriptionFinal)(test: ScalaPactMockConfig => A)(implicit sslContextMap: SslContextMap): A = {
val interactionManager: InteractionManager = new InteractionManager
@@ -82,7 +82,7 @@ waitForServerThenTest(server, mockConfig, test, pactDescription) }
private def waitForServerThenTest[A](server: IPactServer, mockConfig: ScalaPactMockConfig, test: ScalaPactMockConfig => A, pactDescription: ScalaPactDescriptionFinal): A = {
private def waitForServerThenTest[A](server: IPactServer, mockConfig: ScalaPactMockConfig, test: ScalaPactMockConfig => A, pactDescription: ScalaPactDescriptionFinal)(implicit sslContextMap: SslContextMap): A = { def rec(attemptsRemaining: Int, intervalMillis: Int): A = { if(isStubReady(mockConfig)) { val result = configuredTestRunner(pactDescription)(mockConfig)(test) @@ -102,7 +102,7 @@ rec(5, 100) }
private def isStubReady(mockConfig: ScalaPactMockConfig): Boolean = {
--- scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactVerify.scala (date 1508775849000) +++ scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactVerify.scala (date 1510827601000) @@ -4,7 +4,7 @@ import com.itv.scalapactcore.verifier.{PactVerifySettings, Verifier, VersionedConsumer} import java.io.{BufferedWriter, File, FileWriter}
-import com.itv.scalapact.shared.{Helpers, ScalaPactSettings} +import com.itv.scalapact.shared.{Helpers, ScalaPactSettings, SslContextMap} import com.itv.scalapactcore.common.PactReaderWriter._
import scala.concurrent.duration._ @@ -15,14 +15,14 @@
object verifyPact {
def withPactSource(sourceType: PactSourceType): ScalaPactVerifyProviderStates = new ScalaPactVerifyProviderStates(sourceType)
def withPactSource(sourceType: PactSourceType)(implicit sslContextMap: SslContextMap): ScalaPactVerifyProviderStates = new ScalaPactVerifyProviderStates(sourceType)
class ScalaPactVerifyProviderStates(sourceType: PactSourceType) {
class ScalaPactVerifyProviderStates(sourceType: PactSourceType) (implicit sslContextMap: SslContextMap){ def setupProviderState(given: String)(setupProviderState: String => Boolean): ScalaPactVerifyRunner = new ScalaPactVerifyRunner(sourceType, given, setupProviderState) def noSetupRequired: ScalaPactVerifyRunner = new ScalaPactVerifyRunner(sourceType, None, None) }
class ScalaPactVerifyRunner(sourceType: PactSourceType, given: Option[String], setupProviderState: Option[String => Boolean]) {
class ScalaPactVerifyRunner(sourceType: PactSourceType, given: Option[String], setupProviderState: Option[String => Boolean])(implicit sslContextMap: SslContextMap) {
def runStrictVerificationAgainst(port: Int): Unit = doVerification("http", "localhost", port, VerifyTargetConfig.defaultClientTimeout, strict = true) def runStrictVerificationAgainst(port: Int, clientTimeout: Duration): Unit = doVerification("http", "localhost", port, clientTimeout, strict = true) @@ -39,7 +39,7 @@ def runVerificationAgainst(protocol: String, host: String, port: Int, clientTimeout: Duration): Unit = doVerification(protocol, host, port, clientTimeout, strict = false) def runVerificationAgainst(target: VerifyTargetConfig): Unit = doVerification(target.protocol, target.host, target.port, target.clientTimeout, strict = false)
private def doVerification(protocol: String, host: String, port: Int, clientTimeout: Duration, strict: Boolean): Unit = {
private def doVerification(protocol: String, host: String, port: Int, clientTimeout: Duration, strict: Boolean)(implicit sslContextMap: SslContextMap): Unit = {
val providerStateFunc = given.flatMap( g => setupProviderState).getOrElse({ _ : String => true})
--- scalapact-shared/src/main/scala/com/itv/scalapact/shared/IScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-shared/src/main/scala/com/itv/scalapact/shared/IScalaPactHttpClient.scala (date 1510827601000) @@ -5,11 +5,11 @@
trait IScalaPactHttpClient {
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse]
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse]
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse]
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration)(implicit sslContextMap: SslContextMap): Future[InteractionResponse]
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse]
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse]
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse]
--- scalapact-shared/src/main/scala/com/itv/scalapact/shared/SslContextMap.scala (date 1510904148000) +++ scalapact-shared/src/main/scala/com/itv/scalapact/shared/SslContextMap.scala (date 1510904148000) @@ -0,0 +1,28 @@ +package com.itv.scalapact.shared
+import javax.net.ssl.SSLContext
+class SslContextMap(map: Map[String, SSLContext]) extends (String => Option[SSLContext]) {
override def apply(name: String) = map.get(name)
def legalValues = map.keys.toList.sorted +}
+class SslContextNotFoundException(name: String, sslContextMap: SslContextMap) extends Exception(s"SslContext [$name] not found. Legal values are [${sslContextMap.legalValues}]")
+object SslContextMap {
val sslContextHeaderName: String = "pact-ssl-context"
def apply[T](simpleRequest: SimpleRequest)(fn: Option[SSLContext] => SimpleRequest => T)(implicit sslContextMap: SslContextMap): T = {
val sslContext = simpleRequest.headers.get(sslContextHeaderName).map { name =>
sslContextMap(name).getOrElse(throw new SslContextNotFoundException(name, sslContextMap))
}
val newRequest = simpleRequest.copy(headers = simpleRequest.headers - sslContextHeaderName)
fn(sslContext)(newRequest)
}
implicit val defaultEmptyContextMap: SslContextMap = new SslContextMap(Map()) +}
--- build.sbt (date 1510827601000) +++ build.sbt (date 1510904148000) @@ -312,6 +312,9 @@ framework_2_11, framework_2_12, standalone,
shared_2_10,
shared_2_11,
--- scalapact-http4s-0-17-0/src/test/scala/com/itv/scalapact/shared/http/Http4sClientHelperSpec.scala (date 1510827601000) +++ scalapact-http4s-0-17-0/src/test/scala/com/itv/scalapact/shared/http/Http4sClientHelperSpec.scala (date 1510904148000) @@ -1,10 +1,13 @@ package com.itv.scalapact.shared.http
+import javax.net.ssl.SSLContext + import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.WireMock import com.github.tomakehurst.wiremock.client.WireMock. import com.github.tomakehurst.wiremock.core.WireMockConfiguration. -import com.itv.scalapact.shared.{HttpMethod, SimpleRequest} +import com.itv.scalapact.shared.{HttpMethod, SimpleRequest, SslContextMap} +import org.scalatest.easymock.EasyMockSugar import org.scalatest.{BeforeAndAfterAll, FunSpec, Matchers}
}
--- scalapact-shared/src/main/scala/com/itv/scalapact/shared/IPactReader.scala (date 1510827601000) +++ scalapact-shared/src/main/scala/com/itv/scalapact/shared/IPactReader.scala (date 1510904148000) @@ -1,6 +1,6 @@ package com.itv.scalapact.shared
-trait IPactReader { +trait IPactReader {
type ReadPactF = String => Either[String, Pact]
--- scalapact-shared/src/test/scala/com/itv/scalapact/shared/HelpersSpec.scala (date 1510827601000) +++ scalapact-shared/src/test/scala/com/itv/scalapact/shared/HelpersSpec.scala (date 1510904148000) @@ -10,7 +10,6 @@
Helpers.pair(List(1, 2, 3, 4)) shouldEqual Map(1 -> 2, 3 -> 4)
Helpers.pair(List("a", "b", "c")) shouldEqual Map("a" -> "b")
- }
it("should be able to pair a list into a list of tuples") {
--- scalapact-shared/src/test/scala/com/itv/scalapact/shared/SslContextMapSpec.scala (date 1510904148000) +++ scalapact-shared/src/test/scala/com/itv/scalapact/shared/SslContextMapSpec.scala (date 1510904148000) @@ -0,0 +1,36 @@ +package com.itv.scalapact.shared + +import javax.net.ssl.SSLContext + +import org.scalatest.easymock.EasyMockSugar +import org.scalatest.{FunSpec, Matchers} + +class SslContextMapSpec extends FunSpec with Matchers { +
+}
I ended up using a header that's in SSLContextMap. This allows me to send different certificates with different interactions. It's a little clunky at pact creation time: you can only set a single ssl context for the mock server, but since (as far as I know) I can only have one ssl context for a HTTP4s server that makes sense
I have it working now. I've validated it against the code we use internally and it makes pacts, includes the ssl context as a header in the pact json. When it does pack mocking it doesn't look for the header, but in pact verification it uses it to select which certificate to use. I don't think the pact stubber uses them properly, but actually I'm not entirely sure how to do that. I'll probably phone you for a chat about it if that's OK
Do you want to look at the code, and check you are roughly OK with it. I'll add some tests for it over the next few days (I've been using the bank test suite because certificates are such a pain to work with)
On 17 November 2017 at 08:49, Phil Rice phil.rice@validoc.org wrote:
Thanks David,
I share your views on the JSON format, although it's interesting to consider raising this as an issue in the coordination group. We won't be the only people with certificate issues, especially as usage grows.
It's not really provider state because it's part of the request.. We actually have to include the certificate when we make the request as well as when we validate. i.e. it's not really 'provider' state at all it's actually consumer state. I started there but couldn't bring myself to do it: it just felt wrong. I've come up with a very simple idea: have a fake header. The fake header has a value which is the lookup into a map. This header is part of the request (good) is in the Json file already (good) and the data it represents is often in the real world put onto headers anyway (i.e. NGINX would rip the data out of the certificate and pass it to the end point as a header. The place in the code that does this is encapsulated in SslContextMap so if we change our mind and decide it's really provider state, it's really simple to sort it out.
The old bodged version is messy and scattered throughout our code. (we didn't really fork it we copied the files we wanted and edited them... as I say it was a bodge). It also didn't modify the json file: we were in charge of both ends. The work is pretty simple I've attached a patch which is WIP. I think it probably works: the compiler says it does. But because it's SSL certificates I'm in the middle of test pain. I just love SSL Engine Errors. The patch base was 'WIP: provider_test example does not work' . I'm not very familiar with making patches so I'm not sure if you apply it to master or to 'add script for release local'
The patch has a default implicit which (I hope: still need to test it) is threaded through the code to the point at which the connections are made. The default implicit says 'there aren't any SSL Contexts'. By setting a header in the request (which is removed from the actual HTTPS call) the pact user can do this. This feels a little clunky so when I've got this working, I might modify the builder to have a 'with ssl context' and only have the json file know about the fake header.
Just as an aside: a separate issue... Our microservices are behind an API gateway. The API gateway is responsible for things like OPTIONS requests, and also modifies the payload a bit... So when we are consuming our microservices as javascript in the browser, the JSON file we produce is guaranteeded to be unverifiable by the microservice. We split it into two using good old AWK JQ SED style of working and end up with two pacts. One for the API gateway, and one for the actual microservice. We've got a solution for this, I just though you'd be interested in the problem
@Summary I'm working on the pull request: Just need to check it actually works.
regards
Phil
On 16 November 2017 at 18:52, Dave Smith notifications@github.com wrote:
Hi Phil,
Easy stuff first:
- Master branch is up to date.
- I believe we shouldn't modify the Pact JSON format because it will be incompatible with other Pact implementations (including the broker).
- I would have thought this would belong in the provider state section, because it's a requirement the provider must satisfy for verification to happen?
Would it be worth you sharing your original bodged version so I can see what was involved? All that follows otherwise is pure guess work! :-)
Less easy stuff, SSL Context:
First a question: Would it be possible to do the consumer tests in vanilla http and the provider verification using SSL? Might make you're life easier if you can given that you can set the provider state, base url, protocol and port at the time you verify. No worries if not, just asking.
Either way, if I understand your requirements properly, then you'll need to supply an SSL context for Http4s to optionally make use of I think. I'm not fully sure of what you need but I'd guess that:
Consumer For the consumer tests there is actually a way to pass optional config to the test framework that, by default could not set a context but you could override it. See: https://github.com/ITV/scala-pact/blob/a07ada3671785c41afecc 5090073cd7df09656df/scalapact-scalatest/src/main/scala/com/i tv/scalapact/ScalaPactForger.scala#L40 and https://github.com/ITV/scala-pact/blob/a07ada3671785c41afecc 5090073cd7df09656df/scalapact-scalatest/src/main/scala/com/i tv/scalapact/ScalaPactForger.scala#L138
You would then need to pass that context all the way down to (yes) every version of Http4s to make use of (probably). I'm afraid there is much duplication down there, it's on my list of things to review ...sometime...
Provider My suspicion is that regardless of which method you're using to do the verification, you'll probably want to somehow add a new option to the pact verify settings (possibly) (https://github.com/ITV/scala- pact/blob/a07ada3671785c41afecc5090073cd7df09656df/ scalapact-scalatest/src/main/scala/com/itv/scalapact/ ScalaPactVerify.scala#L57) saying to use SSL and then some way to build the context per verification (? if it's global you could just put it right in the settings?) from a memento string in the provider state perhaps.
Please don't forget that there are two ways to verify and your solution will need to be ported to both (but I can help with that).
Show me your original version if you can and we can talk again. Otherwise, if you want to send a PR of a dirty but working version we can go from there if you like?
Cheers,
Dave
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/ITV/scala-pact/issues/74#issuecomment-345004161, or mute the thread https://github.com/notifications/unsubscribe-auth/AART8hQh-tZ_aKkB1tAazX0o6nRMLOu-ks5s3HZOgaJpZM4QgIYl .
-- Stuff our lawyer makes us say:
- This isn't a contract, it's just a chat
- This is confidential: If you get this, and you shouldn't, please tell me
-- Stuff our lawyer makes us say:
--- example/provider/delivered_pacts/Consumer_Provider.json (date 1510221100000) +++ example/provider/delivered_pacts/Consumer_Provider.json (date 1510587421000) @@ -7,25 +7,6 @@ }, "interactions" : [ {
--- example/provider_tests/build.sbt (date 1510221100000) +++ example/provider_tests/build.sbt (date 1510587421000) @@ -8,12 +8,16 @@
libraryDependencies ++= Seq(
--- example/provider_tests/delivered_pacts/Consumer_Provider.json (date 1510221100000) +++ example/provider_tests/delivered_pacts/Consumer_Provider.json (date 1510587421000) @@ -7,25 +7,6 @@ }, "interactions" : [ {
--- example/provider_tests/src/main/scala/com/example/provider/Provider.scala (date 1510221100000) +++ example/provider_tests/src/main/scala/com/example/provider/Provider.scala (date 1510587421000) @@ -1,19 +1,16 @@ package com.example.provider
+import org.http4s.circe. +import org.http4s.dsl.io. import org.http4s.util.CaseInsensitiveString
object Provider {
@@ -23,7 +20,7 @@ val nameHeader = request.headers.get(CaseInsensitiveString("Name")).map(_.value)
(acceptHeader, nameHeader) match {
@@ -40,16 +37,5 @@
}
-object ResultResponseImplicits {
case class ResultResponse(count: Int, results: List[String])
-object TokenResponseImplicits {
--- example/provider_tests/src/main/scala/com/example/provider/Server.scala (date 1510221100000) +++ example/provider_tests/src/main/scala/com/example/provider/Server.scala (date 1510587421000) @@ -1,5 +1,6 @@ package com.example.provider
+import cats.effect.IO import org.http4s.server.blaze.BlazeBuilder import org.http4s.server._
@@ -9,7 +10,7 @@ def main(args: Array[String]): Unit = {
// Here we inject the real functions that do all the work
startServer(BusinessLogic.loadPeople, BusinessLogic.generateToken).awaitShutdown()
startServer(BusinessLogic.loadPeople, BusinessLogic.generateToken) }
// This function allows us to start the service while supplying the dependencies @@ -17,13 +18,14 @@ // this function is called directly, say from our test suite, we can inject // any core logic we like, thus side stepping a few pesky little things... // ...like databases.
def startServer(loadPeopleData: String => List[String], genToken: Int => String): Server = {
BlazeBuilder.bindHttp(8080)
def startServer(loadPeopleData: String => List[String], genToken: Int => String): Server[IO] = {
BlazeBuilder[IO].bindHttp(8080) .mountService(Provider.service(loadPeopleData)(genToken), "/")
.run
}
.start
.unsafeRunSync()
}
def stopServer(server: Server): Unit = {
def stopServer(server: Server[IO]): Unit = { server.shutdown }
--- example/provider_tests/src/test/scala/com/example/provider/VerifyContractsSpec.scala (date 1510221100000) +++ example/provider_tests/src/test/scala/com/example/provider/VerifyContractsSpec.scala (date 1510587421000) @@ -1,14 +1,14 @@ package com.example.provider
import com.itv.scalapact.ScalaPactVerify._
class VerifyContractsSpec extends FunSpec with Matchers with BeforeAndAfterAll {
// Their are almost certainly nicer ways to do this.
var runningService: Option[Server] = None
var runningService: Option[Server[IO]] = None
// Before all the tests are run, we start our service, which is the real service // code, the only difference is that the core business logic has been replaced @@ -27,7 +27,7 @@
// Afterwards we need to remember to shut our service down again. override def afterAll(): Unit = {
runningService.foreach(AlternateStartupApproach.stopServer)
runningService.foreach(p => p.shutdownNow()) }
--- scalapact-core/src/main/scala/com/itv/scalapactcore/verifier/Verifier.scala (date 1508775849000) +++ scalapact-core/src/main/scala/com/itv/scalapactcore/verifier/Verifier.scala (date 1511222031000) @@ -11,9 +11,9 @@
object Verifier {
def verify(loadPactFiles: String => ScalaPactSettings => ConfigAndPacts, pactVerifySettings: PactVerifySettings)(implicit pactReader: IPactReader): ScalaPactSettings => Boolean = arguments => {
def verify(loadPactFiles: String => ScalaPactSettings => ConfigAndPacts, pactVerifySettings: PactVerifySettings)(implicit pactReader: IPactReader, sslContextMap: SslContextMap): ScalaPactSettings => Boolean = arguments => {
val pacts: List[Pact] = if(arguments.localPactFilePath.isDefined) {
val pacts: List[Pact] = if (arguments.localPactFilePath.isDefined) {
println(s"Attempting to use local pact files at: '${arguments.localPactFilePath.getOrElse("
val latestPacts : List[Pact] = versionConsumers.map { consumer =>
val latestPacts: List[Pact] = versionConsumers.map { consumer => ValidatedDetails.buildFrom(consumer.name, pactVerifySettings.providerName, pactVerifySettings.pactBrokerAddress, consumer.version) match { case Left(l) => println(l.red) @@ -52,7 +52,7 @@ .providerState .map(p => ProviderState(p, PartialFunction(pactVerifySettings.providerStates)))
val result = (doRequest(arguments, maybeProviderState) andThen attemptMatch(arguments.giveStrictMode, List(interaction)))(interaction.request)
val result = (doRequest(arguments, maybeProviderState) andThen attemptMatch(arguments.giveStrictMode, List(interaction))) (interaction.request)
PactVerifyResultInContext(result, interaction.description)
}
@@ -107,7 +107,7 @@ Left(s) }
private def doRequest(arguments: ScalaPactSettings, maybeProviderState: Option[ProviderState]): InteractionRequest => Either[String, InteractionResponse] = interactionRequest => {
private def doRequest(arguments: ScalaPactSettings, maybeProviderState: Option[ProviderState])(implicit sslContextMap: SslContextMap): InteractionRequest => Either[String, InteractionResponse] = interactionRequest => { val baseUrl = s"${arguments.giveProtocol}://" + arguments.giveHost + ":" + arguments.givePort.toString val clientTimeout = arguments.giveClientTimeout
@@ -120,24 +120,24 @@
val success = ps.f(ps.key)
if(success)
if (success) println(s"Provider state ran successfully".yellow.bold) else println(s"Provider state run failed".red.bold)
println("--------------------".yellow.bold)
if(!success) {
if (!success) { throw new ProviderStateFailure(ps.key) }
case None =>
// No provider state run needed
// No provider state run needed }
} catch { case t: Throwable =>
if(maybeProviderState.isDefined) {
if (maybeProviderState.isDefined) {
println(s"Error executing unknown provider state function with key: ${maybeProviderState.map(.key).getOrElse("
ScalaPactHttpClient.doInteractionRequestSync(baseUrl, interactionRequest, clientTimeout)
ScalaPactHttpClient.doInteractionRequestSync(baseUrl, interactionRequest.withoutSslContextHeader, clientTimeout, interactionRequest.sslContextName) .leftMap { t => println(s"Error in response: ${t.getMessage}".red) t.getMessage @@ -165,10 +166,9 @@
}
private def fetchAndReadPact(address: String)(implicit pactReader: IPactReader): Option[Pact] = {
private def fetchAndReadPact(address: String)(implicit pactReader: IPactReader, sslContextMap: SslContextMap): Option[Pact] = { println(s"Attempting to fetch pact from pact broker at: $address".white.bold)
ScalaPactHttpClient.doRequestSync(SimpleRequest(address, "", HttpMethod.GET, Map("Accept" -> "application/json"), None)).map {
ScalaPactHttpClient.doRequestSync(SimpleRequest(address, "", HttpMethod.GET, Map("Accept" -> "application/json"), None, sslContextName = None)).map { case r: SimpleResponse if r.is2xx => val pact = r.body.map(pactReader.jsonStringToPact).flatMap { case Right(p) => Option(p) @@ -177,7 +177,7 @@ None }
if(pact.isEmpty) {
if (pact.isEmpty) { println("Could not convert good response to Pact:\n" + r.body.getOrElse("")) pact } else pact @@ -204,7 +204,9 @@ class ProviderStateFailure(key: String) extends Exception()
case class ProviderState(key: String, f: String => Boolean)
case class VersionedConsumer(name: String, version: String)
case class PactVerifySettings(providerStates: (String => Boolean), pactBrokerAddress: String, projectVersion: String, providerName: String, consumerNames: List[String], versionedConsumerNames: List[VersionedConsumer])
case class ValidatedDetails(validatedAddress: ValidPactBrokerAddress, providerName: String, consumerName: String, consumerVersion: String) @@ -213,8 +215,8 @@
def buildFrom(consumerName: String, providerName: String, pactBrokerAddress: String, consumerVersion: String): Either[String, ValidatedDetails] = for {
consumerName <- Helpers.urlEncode(consumerName)
providerName <- Helpers.urlEncode(providerName)
consumerName <- Helpers.urlEncode(consumerName)
providerName <- Helpers.urlEncode(providerName) validatedAddress <- PactBrokerAddressValidation.checkPactBrokerAddress(pactBrokerAddress) } yield ValidatedDetails(validatedAddress, providerName, consumerName, consumerVersion)
--- scalapact-http4s-0-15-0a/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1508775849000) +++ scalapact-http4s-0-15-0a/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1510827601000) @@ -1,5 +1,7 @@ package com.itv.scalapact.shared.http
+import javax.net.ssl.SSLContext + import com.itv.scalapact.shared.{SimpleRequest, SimpleResponse} import org.http4s.{BuildInfo, Response} import org.http4s.client.Client @@ -13,21 +15,22 @@
import HeaderImplicitConversions._
private def blazeClientConfig(clientTimeout: Duration): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
private def blazeClientConfig(clientTimeout: Duration, sslContext: Option[SSLContext]): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
requestTimeout = clientTimeout,
userAgent = Option(User-Agent
(AgentProduct("scala-pact", Option(BuildInfo.version)))),
endpointAuthentication = false,
customExecutor = None
customExecutor = None,
sslContext = sslContext )
private val extractResponse: Response => Task[SimpleResponse] = r => r.bodyAsText.runLog[Task, String].map(_.mkString).map { b => SimpleResponse(r.status.code, r.headers, Some(b)) }
def defaultClient: Client =
buildPooledBlazeHttpClient(1, Duration(1, SECONDS))
buildPooledBlazeHttpClient(1, Duration(1, SECONDS), sslContext=None)
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout))
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration, sslContext: Option[SSLContext]): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout, sslContext ))
--- scalapact-http4s-0-15-0a/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-http4s-0-15-0a/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1511222031000) @@ -28,32 +28,39 @@
val maxTotalConnections: Int = 1
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse] =
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Future[InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout,sslContextName)
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse] =
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest).unsafePerformSyncAttempt.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout).unsafePerformSyncAttempt.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Either[Throwable, InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout,sslContextName).unsafePerformSyncAttempt.toEither
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest): Task[SimpleResponse] =
performRequest(simpleRequest, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds))
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Task[SimpleResponse] =
SslContextMap(simpleRequest) { sslContext =>
simpleRequestWithoutFakeheader =>
println(s"doRequestTask${simpleRequest} ==> ${simpleRequestWithoutFakeheader}")
performRequest(simpleRequestWithoutFakeheader, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds, sslContext))
}
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration): Task[InteractionResponse] =
performRequest(
SimpleRequest( url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body),
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if(r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Task[InteractionResponse] =
SslContextMap(SimpleRequest(url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body, sslContextName)) {
sslContext =>
simpleRequestWithoutFakeheader =>
println(s"doInteractionRequestTask ${simpleRequestWithoutFakeheader}")
performRequest(simpleRequestWithoutFakeheader,
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout, sslContext)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if (r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
} }
--- scalapact-http4s-0-16-2/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-http4s-0-16-2/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1511222031000) @@ -28,24 +28,24 @@
val maxTotalConnections: Int = 1
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse] =
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Future[InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout, sslContextName)
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse] =
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest).attemptRun.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout).attemptRun.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Either[Throwable, InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout,sslContextName).attemptRun.toEither
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest): Task[SimpleResponse] =
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Task[SimpleResponse] = performRequest(simpleRequest, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds))
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration): Task[InteractionResponse] =
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Task[InteractionResponse] = performRequest(
SimpleRequest( url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body),
--- scalapact-http4s-0-16-2a/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1508775849000) +++ scalapact-http4s-0-16-2a/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1510827601000) @@ -1,6 +1,8 @@ package com.itv.scalapact.shared.http
-import com.itv.scalapact.shared.{SimpleRequest, SimpleResponse} +import javax.net.ssl.SSLContext + +import com.itv.scalapact.shared.{SimpleRequest, SimpleResponse, SslContextMap} import org.http4s.{BuildInfo, Response} import org.http4s.client.Client import org.http4s.client.blaze.{BlazeClientConfig, PooledHttp1Client} @@ -13,20 +15,21 @@
import HeaderImplicitConversions._
private def blazeClientConfig(clientTimeout: Duration): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
private def blazeClientConfig(clientTimeout: Duration, sslContext: Option[SSLContext]): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
requestTimeout = clientTimeout,
userAgent = Option(User-Agent
(AgentProduct("scala-pact", Option(BuildInfo.version)))),
checkEndpointIdentification = false
checkEndpointIdentification = false,
sslContext=sslContext )
private val extractResponse: Response => Task[SimpleResponse] = r => r.bodyAsText.runLog[Task, String].map(_.mkString).map { b => SimpleResponse(r.status.code, r.headers, Some(b)) }
def defaultClient: Client =
buildPooledBlazeHttpClient(1, Duration(1, SECONDS))
buildPooledBlazeHttpClient(1, Duration(1, SECONDS), sslContext = None)
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout))
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration,sslContext: Option[SSLContext]): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout, sslContext))
--- scalapact-http4s-0-16-2a/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-http4s-0-16-2a/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1511222031000) @@ -28,32 +28,34 @@
val maxTotalConnections: Int = 1
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse] =
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration,sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Future[InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout, sslContextName)
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse] =
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest).unsafePerformSyncAttempt.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout).unsafePerformSyncAttempt.toEither
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration,sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Either[Throwable, InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout,sslContextName).unsafePerformSyncAttempt.toEither
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest): Task[SimpleResponse] =
performRequest(simpleRequest, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds))
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Task[SimpleResponse] =
SslContextMap(simpleRequest)(sslContext => simpleRequestWithoutFakeHeader => performRequest(simpleRequestWithoutFakeHeader, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds, sslContext)))
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration): Task[InteractionResponse] =
performRequest(
SimpleRequest( url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body),
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if(r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Task[InteractionResponse] =
SslContextMap(SimpleRequest(url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body,sslContextName)) {
(sslContext => simpleRequestWithoutFakeHeader => performRequest(
simpleRequestWithoutFakeHeader,
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout, sslContext)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if (r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
}) }
--- scalapact-http4s-0-17-0/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1508775849000) +++ scalapact-http4s-0-17-0/src/main/scala/com/itv/scalapact/shared/http/Http4sClientHelper.scala (date 1510827601000) @@ -1,5 +1,7 @@ package com.itv.scalapact.shared.http
+import javax.net.ssl.SSLContext + import com.itv.scalapact.shared.{SimpleRequest, SimpleResponse} import fs2.{Strategy, Task} import org.http4s.client.Client @@ -16,8 +18,9 @@
private[http] implicit val strategy: Strategy = fs2.Strategy.fromFixedDaemonPool(2, threadName = "strategy")
private def blazeClientConfig(clientTimeout: Duration): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy(
private def blazeClientConfig(clientTimeout: Duration, sslContext: Option[SSLContext]): BlazeClientConfig = BlazeClientConfig.defaultConfig.copy( requestTimeout = clientTimeout,
sslContext=sslContext,
userAgent = Option(User-Agent
(AgentProduct("scala-pact", Option(BuildInfo.version)))),
checkEndpointIdentification = false
)
@@ -26,16 +29,16 @@
r.bodyAsText.runLog.map(_.mkString).map { b => SimpleResponse(r.status.code, r.headers, Some(b)) }
def defaultClient: Client =
buildPooledBlazeHttpClient(1, Duration(1, SECONDS))
buildPooledBlazeHttpClient(1, Duration(1, SECONDS), sslContext = None)
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout))
def buildPooledBlazeHttpClient(maxTotalConnections: Int, clientTimeout: Duration, sslContext: Option[SSLContext]): Client =
PooledHttp1Client(maxTotalConnections, blazeClientConfig(clientTimeout,sslContext))
val doRequest: (SimpleRequest, Client) => Task[SimpleResponse] = (request, httpClient) => for {
request <- Http4sRequestResponseFactory.buildRequest(request)
request <- Http4sRequestResponseFactory.buildRequest(request) response <- httpClient.fetchSimpleResponse(extractResponse)
_ <- httpClient.shutdown
_ <- httpClient.shutdown } yield response
--- scalapact-http4s-0-17-0/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1508775849000) +++ scalapact-http4s-0-17-0/src/main/scala/com/itv/scalapact/shared/http/ScalaPactHttpClient.scala (date 1511222031000) @@ -31,32 +31,37 @@
private implicit val strategy: Strategy = Http4sClientHelper.strategy
def doRequest(simpleRequest: SimpleRequest): Future[SimpleResponse] =
def doRequest(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Future[SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration): Future[InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout)
def doInteractionRequest(url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Future[InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout, sslContextName: Option[String])
def doRequestSync(simpleRequest: SimpleRequest): Either[Throwable, SimpleResponse] =
def doRequestSync(simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Either[Throwable, SimpleResponse] = doRequestTask(Http4sClientHelper.doRequest, simpleRequest).unsafeAttemptRun()
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration): Either[Throwable, InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout).unsafeAttemptRun()
def doInteractionRequestSync(url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Either[Throwable, InteractionResponse] =
doInteractionRequestTask(Http4sClientHelper.doRequest, url, ir, clientTimeout, sslContextName).unsafeAttemptRun()
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest): Task[SimpleResponse] =
performRequest(simpleRequest, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds))
def doRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], simpleRequest: SimpleRequest)(implicit sslContextMap: SslContextMap): Task[SimpleResponse] =
SslContextMap(simpleRequest)(sslContext => simpleRequestWithoutFakeheader => performRequest(simpleRequestWithoutFakeheader, Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, 2.seconds, sslContext)))
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration): Task[InteractionResponse] =
performRequest(
SimpleRequest( url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body),
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if(r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
def doInteractionRequestTask(performRequest: (SimpleRequest, Client) => Task[SimpleResponse], url: String, ir: InteractionRequest, clientTimeout: Duration, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Task[InteractionResponse] =
SslContextMap(SimpleRequest(url, ir.path.getOrElse("") + ir.query.map(q => s"?$q").getOrElse(""), HttpMethod.maybeMethodToMethod(ir.method), ir.headers.getOrElse(Map.empty[String, String]), ir.body, sslContextName)) {
sslContext =>
simpleRequestWithoutFakeheader =>
performRequest(
simpleRequestWithoutFakeheader,
Http4sClientHelper.buildPooledBlazeHttpClient(maxTotalConnections, clientTimeout, sslContext)
).map { r =>
InteractionResponse(
status = Option(r.statusCode),
headers = if (r.headers.isEmpty) None else Option(r.headers.map(p => p._1 -> p._2.mkString)),
body = r.body,
matchingRules = None
)
} }
--- scalapact-sbtplugin/src/main/scala/com/itv/scalapact/plugin/verifier/ScalaPactVerifyCommand.scala (date 1508775849000) +++ scalapact-sbtplugin/src/main/scala/com/itv/scalapact/plugin/verifier/ScalaPactVerifyCommand.scala (date 1511222031000) @@ -3,7 +3,7 @@ import com.itv.scalapact.plugin.ScalaPactPlugin import com.itv.scalapactcore.verifier.ProviderState import com.itv.scalapact.shared.ColourOuput. -import com.itv.scalapact.shared.ScalaPactSettings +import com.itv.scalapact.shared.{ScalaPactSettings, SslContextMap} import com.itv.scalapactcore.common.LocalPactFileLoader import com.itv.scalapactcore.verifier. import sbt._ @@ -55,7 +55,7 @@ .map(t => VersionedConsumer(t._1, t._2)) )
val successfullyVerified = verify(LocalPactFileLoader.loadPactFiles(pactReader)(true), pactVerifySettings)(pactReader)(scalaPactSettings)
val successfullyVerified = verify(LocalPactFileLoader.loadPactFiles(pactReader)(true), pactVerifySettings)(pactReader, new SslContextMap(Map()))(scalaPactSettings)
if (successfullyVerified) sys.exit(0) else sys.exit(1)
--- scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactForger.scala (date 1508775849000) +++ scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactForger.scala (date 1511222031000) @@ -1,11 +1,14 @@ package com.itv.scalapact
+import com.itv.scalapact.shared.SslContextMap + import scala.language.implicitConversions import scala.util.Properties
object ScalaPactForger {
implicit def toOption[A](a: A): Option[A] = Option(a) + implicit def rulesToOptionalList(rules: ScalaPactForger.ScalaPactMatchingRules): Option[List[ScalaPactForger.ScalaPactMatchingRule]] = Option(rules.rules)
@@ -25,23 +28,27 @@ def between(consumer: String): ScalaPartialPact = new ScalaPartialPact(consumer)
class ScalaPartialPact(consumer: String) {
def and(provider: String): ScalaPactDescription = new ScalaPactDescription(consumer, provider, Nil)
def and(provider: String): ScalaPactDescription = new ScalaPactDescription(consumer, provider, None, Nil) }
class ScalaPactDescription(consumer: String, provider: String, interactions: List[ScalaPactInteraction]) {
class ScalaPactDescription(consumer: String, provider: String, sslContextName: Option[String], interactions: List[ScalaPactInteraction]) {
/**
*
def addInteraction(interaction: ScalaPactInteraction): ScalaPactDescription = new ScalaPactDescription(consumer, provider, interactions ++ List(interaction))
def addInteraction(interaction: ScalaPactInteraction): ScalaPactDescription = new ScalaPactDescription(consumer, provider, sslContextName, interactions ++ List(interaction))
def runConsumerTest[A](test: ScalaPactMockConfig => A)(implicit options: ScalaPactOptions): A = {
def addSslContextName(name: String) = new ScalaPactDescription(consumer, provider, Some(name), interactions)
def runConsumerTest[A](test: ScalaPactMockConfig => A)(implicit options: ScalaPactOptions, sslContextMap: SslContextMap): A = { ScalaPactMock.runConsumerIntegrationTest(strict)( ScalaPactDescriptionFinal( consumer, provider,
sslContextName, interactions.map(i => i.finalise), options ) @@ -49,6 +56,7 @@ }
}
}
object interaction { @@ -60,8 +68,11 @@
def uponReceiving(path: String): ScalaPactInteraction = uponReceiving(GET, path, None, Map.empty, None, None)
def uponReceiving(method: ScalaPactMethod, path: String): ScalaPactInteraction = uponReceiving(method, path, None, Map.empty, None, None)
def uponReceiving(method: ScalaPactMethod, path: String, query: Option[String]): ScalaPactInteraction = uponReceiving(method, path, query, Map.empty, None, None)
def uponReceiving(method: ScalaPactMethod, path: String, query: Option[String], headers: Map[String, String], body: Option[String], matchingRules: Option[List[ScalaPactMatchingRule]]): ScalaPactInteraction = new ScalaPactInteraction( description, providerState, @@ -70,8 +81,11 @@ )
def willRespondWith(status: Int): ScalaPactInteraction = willRespondWith(status, Map.empty, None, None)
def willRespondWith(status: Int, body: String): ScalaPactInteraction = willRespondWith(status, Map.empty, Option(body), None)
def willRespondWith(status: Int, headers: Map[String, String], body: String): ScalaPactInteraction = willRespondWith(status, headers, Option(body), None)
def willRespondWith(status: Int, headers: Map[String, String], body: Option[String], matchingRules: Option[List[ScalaPactMatchingRule]]): ScalaPactInteraction = new ScalaPactInteraction( description, providerState, @@ -82,19 +96,26 @@ def finalise: ScalaPactInteractionFinal = ScalaPactInteractionFinal(description, providerState, request, response) }
case class ScalaPactDescriptionFinal(consumer: String, provider: String, interactions: List[ScalaPactInteractionFinal], options: ScalaPactOptions)
case class ScalaPactDescriptionFinal(consumer: String, provider: String, sslContextName: Option[String], interactions: List[ScalaPactInteractionFinal], options: ScalaPactOptions) {
def withHeaderForSsl = sslContextName.fold(this)(name => copy(interactions = interactions.map(i => i.copy(request = i.request.copy(headers = i.request.headers + (SslContextMap.sslContextHeaderName -> name))))))
}
case class ScalaPactInteractionFinal(description: String, providerState: Option[String], request: ScalaPactRequest, response: ScalaPactResponse)
object ScalaPactRequest { val default: ScalaPactRequest = ScalaPactRequest(GET, "/", None, Map.empty, None, None) }
case class ScalaPactRequest(method: ScalaPactMethod, path: String, query: Option[String], headers: Map[String, String], body: Option[String], matchingRules: Option[List[ScalaPactMatchingRule]])
sealed trait ScalaPactMatchingRule { val key: String }
case class ScalaPactMatchingRuleRegex(key: String, regex: String) extends ScalaPactMatchingRule
case class ScalaPactMatchingRuleType(key: String) extends ScalaPactMatchingRule
case class ScalaPactMatchingRuleArrayMinLength(key: String, minimum: Int) extends ScalaPactMatchingRule
case class ScalaPactMatchingRules(rules: List[ScalaPactMatchingRule]) { @@ -130,20 +151,37 @@ object ScalaPactResponse { val default: ScalaPactResponse = ScalaPactResponse(200, Map.empty, None, None) }
case class ScalaPactResponse(status: Int, headers: Map[String, String], body: Option[String], matchingRules: Option[List[ScalaPactMatchingRule]])
object ScalaPactOptions { val DefaultOptions: ScalaPactOptions = ScalaPactOptions(writePactFiles = true, outputPath = Properties.envOrElse("pact.rootDir", "target/pacts")) }
case class ScalaPactOptions(writePactFiles: Boolean, outputPath: String)
sealed trait ScalaPactMethod { val method: String }
case object GET extends ScalaPactMethod { val method = "GET" }
case object PUT extends ScalaPactMethod { val method = "PUT" }
case object POST extends ScalaPactMethod { val method = "POST" }
case object DELETE extends ScalaPactMethod { val method = "DELETE" }
case object OPTIONS extends ScalaPactMethod { val method = "OPTIONS" }
case object GET extends ScalaPactMethod {
val method = "GET"
}
case object PUT extends ScalaPactMethod {
val method = "PUT"
}
case object POST extends ScalaPactMethod {
val method = "POST"
}
case object DELETE extends ScalaPactMethod {
val method = "DELETE"
}
case object OPTIONS extends ScalaPactMethod {
val method = "OPTIONS"
}
--- scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactMock.scala (date 1508775849000) +++ scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactMock.scala (date 1511222031000) @@ -14,10 +14,10 @@
object ScalaPactMock {
private def configuredTestRunner[A](pactDescription: ScalaPactDescriptionFinal)(config: ScalaPactMockConfig)(test: => ScalaPactMockConfig => A): A = {
private def configuredTestRunner[A](pactDescription: ScalaPactDescriptionFinal)(config: ScalaPactMockConfig)(test: => ScalaPactMockConfig => A)(implicit sslContextMap: SslContextMap): A = {
if(pactDescription.options.writePactFiles) {
ScalaPactContractWriter.writePactContracts(config.outputPath)(pactWriter)(pactDescription)
if (pactDescription.options.writePactFiles) {
ScalaPactContractWriter.writePactContracts(config.outputPath)(pactWriter)(pactDescription.withHeaderForSsl) }
test(config) @@ -38,7 +38,7 @@ // Ignore IOException on close() case _: IOException => }
} catch{
} catch { case _: IOException => } finally { if (socket != null) { @@ -50,16 +50,16 @@ } }
if(port == -1) throw new IllegalStateException("Could not find a free TCP/IP port to start embedded HTTP Server on")
if (port == -1) throw new IllegalStateException("Could not find a free TCP/IP port to start embedded HTTP Server on") else port }
def runConsumerIntegrationTest[A](strict: Boolean)(pactDescription: ScalaPactDescriptionFinal)(test: ScalaPactMockConfig => A): A = {
def runConsumerIntegrationTest[A](strict: Boolean)(pactDescription: ScalaPactDescriptionFinal)(test: ScalaPactMockConfig => A)(implicit sslContextMap: SslContextMap): A = {
val interactionManager: InteractionManager = new InteractionManager
val mockConfig = ScalaPactMockConfig("http", "localhost", findFreePort(), pactDescription.options.outputPath)
val protocol = pactDescription.sslContextName.fold("http")(_ => "https")
val mockConfig = ScalaPactMockConfig(protocol, "localhost", findFreePort(), pactDescription.options.outputPath, pactDescription.sslContextName) val configAndPacts: ConfigAndPacts = ConfigAndPacts( scalaPactSettings = ScalaPactSettings( host = Option(mockConfig.host), @@ -75,22 +75,22 @@
val connectionPoolSize: Int = 5
val server: IPactServer = (interactionManager.addToInteractionManager andThen runServer(interactionManager, connectionPoolSize))(configAndPacts)
val server: IPactServer = (interactionManager.addToInteractionManager andThen runServer(interactionManager, connectionPoolSize, pactDescription.sslContextName, configAndPacts.scalaPactSettings.givePort)) (configAndPacts)
println("> ScalaPact stub running at: " + mockConfig.baseUrl)
waitForServerThenTest(server, mockConfig, test, pactDescription) }
private def waitForServerThenTest[A](server: IPactServer, mockConfig: ScalaPactMockConfig, test: ScalaPactMockConfig => A, pactDescription: ScalaPactDescriptionFinal): A = {
private def waitForServerThenTest[A](server: IPactServer, mockConfig: ScalaPactMockConfig, test: ScalaPactMockConfig => A, pactDescription: ScalaPactDescriptionFinal)(implicit sslContextMap: SslContextMap): A = { def rec(attemptsRemaining: Int, intervalMillis: Int): A = {
if(isStubReady(mockConfig)) {
if (isStubReady(mockConfig, pactDescription.sslContextName)) { val result = configuredTestRunner(pactDescription)(mockConfig)(test)
stopServer(server)
result
} else if(attemptsRemaining == 0) {
} else if (attemptsRemaining == 0) { throw new Exception("Could not connect to stub are: " + mockConfig.baseUrl) } else { println("> ...waiting for stub, attempts remaining: " + attemptsRemaining.toString) @@ -102,8 +102,8 @@ rec(5, 100) }
private def isStubReady(mockConfig: ScalaPactMockConfig): Boolean = {
ScalaPactHttpClient.doRequestSync(SimpleRequest(mockConfig.baseUrl, "/stub/status", HttpMethod.GET, Map("X-Pact-Admin" -> "true"), None)) match {
private def isStubReady(mockConfig: ScalaPactMockConfig, sslContextName: Option[String])(implicit sslContextMap: SslContextMap): Boolean = {
ScalaPactHttpClient.doRequestSync(SimpleRequest(mockConfig.baseUrl, "/stub/status", HttpMethod.GET, Map("X-Pact-Admin" -> "true"), None, sslContextName=sslContextName)) match { case Left(_) => false
@@ -114,6 +114,6 @@
}
--- scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactVerify.scala (date 1508775849000) +++ scalapact-scalatest/src/main/scala/com/itv/scalapact/ScalaPactVerify.scala (date 1511222031000) @@ -4,7 +4,7 @@ import com.itv.scalapactcore.verifier.{PactVerifySettings, Verifier, VersionedConsumer} import java.io.{BufferedWriter, File, FileWriter}
-import com.itv.scalapact.shared.{Helpers, ScalaPactSettings} +import com.itv.scalapact.shared.{Helpers, ScalaPactSettings, SslContextMap} import com.itv.scalapactcore.common.PactReaderWriter._
import scala.concurrent.duration._ @@ -15,14 +15,14 @@
object verifyPact {
def withPactSource(sourceType: PactSourceType): ScalaPactVerifyProviderStates = new ScalaPactVerifyProviderStates(sourceType)
def withPactSource(sourceType: PactSourceType)(implicit sslContextMap: SslContextMap): ScalaPactVerifyProviderStates = new ScalaPactVerifyProviderStates(sourceType)
class ScalaPactVerifyProviderStates(sourceType: PactSourceType) {
class ScalaPactVerifyProviderStates(sourceType: PactSourceType) (implicit sslContextMap: SslContextMap){ def setupProviderState(given: String)(setupProviderState: String => Boolean): ScalaPactVerifyRunner = new ScalaPactVerifyRunner(sourceType, given, setupProviderState) def noSetupRequired: ScalaPactVerifyRunner = new ScalaPactVerifyRunner(sourceType, None, None) }
class ScalaPactVerifyRunner(sourceType: PactSourceType, given: Option[String], setupProviderState: Option[String => Boolean]) {
class ScalaPactVerifyRunner(sourceType: PactSourceType, given: Option[String], setupProviderState: Option[String => Boolean])(implicit sslContextMap: SslContextMap) {
--- scalapact-shared/src/main/scala/com/itv/scalapact/sha
Hi Phil, I'll send you an email.
Merged and released in version 2.2.2.
At the company I am working for, we are now adopting pacts. We need to be able to specify the SSL Context for the pacts. This is because the scala microservices (both producers and consumers) communicate using SSL and have client side certificates. The client side certificates hold data which impacts the response
We've forked scala pact (many versions ago) and 'bodged' this in. We'd like to now do it properly and make it a pull request. Scala pact has changed quite dramatically since the version we were using, so I'd like to chat about how to do this properly
My feeling is that ideally we would be able to specify 'which' SSL context we are going to use in while making the pacts and while verifying. I'd ideally add an 'sslcontext' name in the JSON pact file, but I believe that making changes to that file format is beyond the scope of this project. An other obvious other place is provider context or description and having a parser that looks for `[sslContext=name]' in them. I'm also considering using a 'fake header'
If I do this should I start from the master branch, or is there a better branch?
Do I need to do this for all the different 'frameworks' / versions? or can I just do it for the more upto date version?