Skip to content

Commit 160ad01

Browse files
author
jbwheatley
authored
Implement new pact-broker "pacts-for-verification" feature (#178)
* new pact source type * break up logic in Verifier * add example showing pacts-for-verification working * update test-examples script
1 parent 0a65a8d commit 160ad01

File tree

64 files changed

+966
-386
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+966
-386
lines changed

example/consumer/build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ lazy val pactVersionFile: SettingKey[File] = settingKey[File]("location of scala
1414
pactVersionFile := baseDirectory.value.getParentFile.getParentFile / "version.sbt"
1515

1616
libraryDependencies ++= {
17+
//A hack so we don't have to manually update the scala-pact version for the examples
1718
lazy val pactVersion = IO.read(pactVersionFile.value).split('"')(1)
1819
Seq(
1920
"com.itv" %% "scalapact-circe-0-13" % pactVersion % "test",

example/consumer/pact.sbt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//For publishing to pact-broker test server (these credentials are public knowledge)
2+
3+
pactBrokerAddress := "https://test.pact.dius.com.au"
4+
pactBrokerCredentials := ("dXfltyFMgNOFZAxr8io9wJ37iUpY42M", "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1")
5+
pactContractVersion := "0.2.0"
6+
pactContractTags := Seq("example")

example/consumer/project/plugins.sbt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import java.io.File
22

33
import sbt.Defaults.sbtPluginExtra
44

5+
/*
6+
This is just so we don't need to set the version manually for the examples. In reality you should just do
7+
8+
addSbtPlugin("com.itv" % "sbt-scalapact" % scalaPactVersion)
9+
*/
10+
511
lazy val pactVersionFile: SettingKey[File] = settingKey[File]("location of scala-pact version for examples")
612
pactVersionFile := baseDirectory.value.getParentFile.getParentFile.getParentFile / "version.sbt"
713

example/consumer/src/test/scala/com/example/consumer/ProviderClientSpec.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class ProviderClientSpec extends FunSpec with Matchers {
1818

1919
implicit val formats = DefaultFormats
2020

21+
val CONSUMER = "scala-pact-consumer"
22+
val PROVIDER = "scala-pact-provider"
23+
2124
describe("Connecting to the Provider service") {
2225

2326
it("should be able to fetch results") {
@@ -32,8 +35,8 @@ class ProviderClientSpec extends FunSpec with Matchers {
3235
)
3336

3437
forgePact
35-
.between("Consumer")
36-
.and("Provider")
38+
.between(CONSUMER)
39+
.and(PROVIDER)
3740
.addInteraction(
3841
interaction
3942
.description("Fetching results")
@@ -54,8 +57,8 @@ class ProviderClientSpec extends FunSpec with Matchers {
5457

5558
it("should be able to get an auth token") {
5659
forgePact
57-
.between("Consumer")
58-
.and("Provider")
60+
.between(CONSUMER)
61+
.and(PROVIDER)
5962
.addInteraction(
6063
interaction
6164
.description("Fetching least secure auth token ever")

example/deliver.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
echo "Attempting to deliver pact file"
44

5-
PACT_NAME="Consumer_Provider.json"
5+
PACT_NAME="scala-pact-consumer_scala-pact-provider.json"
66
PACT_FILE="consumer/target/pacts/$PACT_NAME"
77

88
if [ ! -f $PACT_FILE ]

example/provider/delivered_pacts/Consumer_Provider.json renamed to example/provider/delivered_pacts/scala-pact-consumer_scala-pact-provider.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
{
22
"provider" : {
3-
"name" : "Provider"
3+
"name" : "scala-pact-provider"
4+
},
5+
"consumer" : {
6+
"name" : "scala-pact-consumer"
47
},
58
"interactions" : [
69
{
10+
"description" : "Fetching least secure auth token ever",
711
"request" : {
812
"method" : "GET",
913
"path" : "/auth_token",
14+
"headers" : {
15+
"Accept" : "application/json",
16+
"Name" : "Bob"
17+
},
1018
"matchingRules" : {
1119
"$.headers.Name" : {
1220
"match" : "regex",
1321
"regex" : "^([a-zA-Z]+)$"
1422
}
15-
},
16-
"headers" : {
17-
"Accept" : "application/json",
18-
"Name" : "Bob"
1923
}
2024
},
21-
"description" : "Fetching least secure auth token ever",
2225
"response" : {
2326
"status" : 202,
2427
"headers" : {
@@ -36,11 +39,12 @@
3639
}
3740
},
3841
{
42+
"providerState" : "Results: Bob, Fred, Harry",
43+
"description" : "Fetching results",
3944
"request" : {
4045
"method" : "GET",
4146
"path" : "/results"
4247
},
43-
"description" : "Fetching results",
4448
"response" : {
4549
"status" : 200,
4650
"headers" : {
@@ -54,13 +58,9 @@
5458
"Harry"
5559
]
5660
}
57-
},
58-
"providerState" : "Results: Bob, Fred, Harry"
61+
}
5962
}
6063
],
61-
"consumer" : {
62-
"name" : "Consumer"
63-
},
6464
"metadata" : {
6565
"pactSpecification" : {
6666
"version" : "2.0.0"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
style = defaultWithAlign
2+
maxColumn = 120
3+
rewrite.rules = [RedundantBraces,RedundantParens,PreferCurlyFors]
4+
danglingParentheses = true
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import java.io.File
2+
3+
organization := "com.example"
4+
5+
name := "provider_pacts-for-verification"
6+
7+
8+
scalaVersion := "2.13.3"
9+
10+
lazy val pactVersionFile: SettingKey[File] = settingKey[File]("location of scala-pact version for examples")
11+
pactVersionFile := baseDirectory.value.getParentFile.getParentFile / "version.sbt"
12+
13+
libraryDependencies ++= {
14+
//A hack so we don't have to manually update the scala-pact version for the examples
15+
lazy val pactVersion = IO.read(pactVersionFile.value).split('"')(1)
16+
Seq(
17+
"org.http4s" %% "http4s-blaze-server" % "0.21.7",
18+
"org.http4s" %% "http4s-dsl" % "0.21.7",
19+
"org.http4s" %% "http4s-circe" % "0.21.7",
20+
"org.slf4j" % "slf4j-simple" % "1.6.4",
21+
"org.scalatest" %% "scalatest" % "3.0.8" % "test",
22+
"com.itv" %% "scalapact-circe-0-13" % pactVersion % "test",
23+
"com.itv" %% "scalapact-http4s-0-21" % pactVersion % "test",
24+
"com.itv" %% "scalapact-scalatest" % pactVersion % "test",
25+
// Optional for auto-derivation of JSON codecs
26+
"io.circe" %% "circe-generic" % "0.13.0",
27+
// Optional for string interpolation to JSON model
28+
"io.circe" %% "circe-literal" % "0.13.0",
29+
)
30+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Bob,Fred,Harry
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version = 1.3.2
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import java.io.File
2+
3+
import sbt.Defaults.sbtPluginExtra
4+
5+
lazy val pactVersionFile: SettingKey[File] = settingKey[File]("location of scala-pact version for examples")
6+
pactVersionFile := baseDirectory.value.getParentFile.getParentFile.getParentFile / "version.sbt"
7+
8+
libraryDependencies += {
9+
val pactVersion = IO.read(pactVersionFile.value).split('"')(1)
10+
val sbtV = (sbtBinaryVersion in pluginCrossBuild).value
11+
val scalaV = (scalaBinaryVersion in update).value
12+
sbtPluginExtra("com.itv" % "sbt-scalapact" % pactVersion, sbtV, scalaV)
13+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.example.provider
2+
3+
import java.io.File
4+
import scala.io.Source
5+
import scala.util.Random
6+
7+
object BusinessLogic {
8+
9+
def loadPeople(peopleFile: String): List[String] = {
10+
val source = Source.fromFile(new File(peopleFile).toURI)
11+
val people = source
12+
.getLines
13+
.mkString
14+
.split(',')
15+
.toList
16+
17+
source.close()
18+
19+
people
20+
}
21+
22+
def generateToken(requiredLength: Int): String =
23+
Random.alphanumeric.take(requiredLength).mkString
24+
25+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.example.provider
2+
3+
import cats.effect._
4+
import io.circe.generic.auto._
5+
import io.circe.syntax._
6+
import org.http4s._
7+
import org.http4s.circe._
8+
import org.http4s.dsl.io._
9+
import org.http4s.util.CaseInsensitiveString
10+
import org.http4s.implicits._
11+
12+
object Provider {
13+
14+
val service: (String => List[String]) => (Int => String) => HttpApp[IO] = loadPeopleData =>
15+
genToken =>
16+
HttpRoutes.of[IO] {
17+
case request @ GET -> Root / "results" =>
18+
val pactHeader = request.headers.get(CaseInsensitiveString("Pact")).map(_.value).getOrElse("")
19+
20+
Ok(ResultResponse(3, loadPeopleData("people.txt")).asJson).map(_.putHeaders(Header("Pact", pactHeader)))
21+
22+
case request @ GET -> Root / "auth_token" =>
23+
val acceptHeader = request.headers.get(CaseInsensitiveString("Accept")).map(_.value)
24+
val nameHeader = request.headers.get(CaseInsensitiveString("Name")).map(_.value)
25+
26+
(acceptHeader, nameHeader) match {
27+
case (Some(_), Some(_)) =>
28+
val token = genToken(10)
29+
Accepted(Token(token).asJson).map(_.withHeaders(Headers.of(Header("Content-Type", "application/json; charset=UTF-8"))))
30+
31+
case (Some(_), None) =>
32+
BadRequest("Missing name header")
33+
34+
case (None, Some(_)) =>
35+
BadRequest("Missing accept header")
36+
37+
case (None, None) =>
38+
BadRequest("Missing accept and name headers")
39+
}
40+
}.orNotFound
41+
42+
}
43+
44+
case class ResultResponse(count: Int, results: List[String])
45+
case class Token(token: String)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.example.provider
2+
3+
import java.util.concurrent.Executors
4+
5+
import cats.effect.IO
6+
import cats.effect._
7+
import org.http4s.server.blaze.BlazeServerBuilder
8+
import org.http4s.server._
9+
10+
import scala.concurrent.ExecutionContext
11+
12+
object AlternateStartupApproach extends IOApp {
13+
val executionContext = ExecutionContext.fromExecutor(
14+
Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors())
15+
)
16+
17+
// On ordinary start up, this service will begin here.
18+
def run(args: List[String]): IO[ExitCode] =
19+
serverResource(BusinessLogic.loadPeople, BusinessLogic.generateToken)
20+
.use(_ => IO.never).as(ExitCode.Success)
21+
22+
def serverResource(loadPeopleData: String => List[String], genToken: Int => String): Resource[IO, Server[IO]] =
23+
BlazeServerBuilder[IO](executionContext)
24+
.bindHttp(8080)
25+
.withHttpApp(Provider.service(loadPeopleData)(genToken))
26+
.resource
27+
28+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.example.provider
2+
3+
import org.scalatest.{BeforeAndAfterAll, FunSpec, Matchers}
4+
import com.itv.scalapact.ScalaPactVerify._
5+
import com.itv.scalapact.shared.{ConsumerVersionSelector, PactBrokerAuthorization, ProviderStateResult}
6+
7+
import scala.concurrent.duration._
8+
9+
class VerifyContractsSpec extends FunSpec with Matchers with BeforeAndAfterAll {
10+
11+
import com.itv.scalapact.circe13._
12+
import com.itv.scalapact.http4s21._
13+
14+
val serverAllocated =
15+
AlternateStartupApproach.serverResource(_ => List("Bob", "Fred", "Harry"), _ => "abcABC123").allocated.unsafeRunSync()
16+
17+
override def beforeAll(): Unit = {
18+
()
19+
}
20+
21+
override def afterAll(): Unit = {
22+
serverAllocated._2.unsafeRunSync()
23+
}
24+
25+
26+
describe("Verifying Consumer Contracts") {
27+
it("should be able to verify it's contracts") {
28+
val consumer = ConsumerVersionSelector("example", latest = true) // This should fetch all the latest pacts of consumer with tag example
29+
30+
verifyPact
31+
.withPactSource(
32+
pactBrokerWithVersionSelectors(
33+
"https://test.pact.dius.com.au",
34+
"scala-pact-provider",
35+
List(consumer),
36+
List(),
37+
None,
38+
//again, these are publicly known creds for a test pact-broker
39+
PactBrokerAuthorization(pactBrokerCredentials = ("dXfltyFMgNOFZAxr8io9wJ37iUpY42M", "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1"), "")
40+
)
41+
)
42+
.setupProviderState("given") {
43+
case "Results: Bob, Fred, Harry" =>
44+
val newHeader = "Pact" -> "modifiedRequest"
45+
ProviderStateResult(true, req => req.copy(headers = Option(req.headers.fold(Map(newHeader))(_ + newHeader))))
46+
}
47+
.runVerificationAgainst("localhost", 8080, 10.seconds)
48+
}
49+
}
50+
51+
}

example/provider_tests/build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ lazy val pactVersionFile: SettingKey[File] = settingKey[File]("location of scala
1111
pactVersionFile := baseDirectory.value.getParentFile.getParentFile / "version.sbt"
1212

1313
libraryDependencies ++= {
14+
//A hack so we don't have to manually update the scala-pact version for the examples
1415
lazy val pactVersion = IO.read(pactVersionFile.value).split('"')(1)
1516
Seq(
1617
"org.http4s" %% "http4s-blaze-server" % "0.21.7",

0 commit comments

Comments
 (0)