Skip to content

Commit

Permalink
Initial pact commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ghost-in-a-Jar committed Apr 27, 2023
1 parent b568fac commit 7a45c9e
Show file tree
Hide file tree
Showing 8 changed files with 459 additions and 4 deletions.
5 changes: 5 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,10 @@ lazy val `cromwell-drs-localizer` = project
.dependsOn(common)
.dependsOn(`cloud-nio-impl-drs` % "test->test")

lazy val pact4s = project.in(file("pact4s"))
.settings(pact4sSettings)
.dependsOn(services)

lazy val server = project
.withExecutableSettings("cromwell", serverDependencies)
.dependsOn(engine)
Expand Down Expand Up @@ -419,4 +423,5 @@ lazy val root = (project in file("."))
.aggregate(wes2cromwell)
.aggregate(wom)
.aggregate(womtool)
.aggregate(pact4s)
.withAggregateSettings()
51 changes: 51 additions & 0 deletions pact4s/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
c# pact4s [Under construction]

pact4s is used for contract testing.

# Dependencies

```scala
val pact4sDependencies = Seq(
pact4sScalaTest,
pact4sCirce,
http4sEmberClient,
http4sDsl,
http4sEmberServer,
http4sCirce,
circeCore,
typelevelCat,
scalaTest
)

lazy val pact4s = project.in(file("pact4s"))
.settings(pact4sSettings)
.dependsOn(http % "test->test;compile->compile")
```

## Building and running contract tests
Clone the repo.
```
$ git clone https://github.com/broadinstitute/cromwell.git
$ cd cromwell
```

If you are already using OpenJDK 17, run the following command.
```
$ sbt "project pact4s" clean test
```

Otherwise, you can run the command inside a docker container with OpenJDK 17 installed.
This is especially useful when automating contract tests in a GitHub Action runner which does not guarantee the correct OpenJDK version.
```
docker run --rm -v $PWD:/working \
-v jar-cache:/root/.ivy \
-v jar-cache:/root/.ivy2 \
-w /working \
sbtscala/scala-sbt:openjdk-17.0.2_1.7.2_2.13.10 \
sbt "project pact4s" clean test
```

The generated contracts can be found in the `./target/pacts` folder
- `cromwell-consumer-drshub-provider.json`
- `cromwell-consumer-fake-provider.json`

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.broadinstitute.dsde.workbench.cromwell.consumer

import cats.effect.Concurrent
import cats.syntax.all._
import org.http4s.Credentials.Token
import org.http4s._
import org.http4s.client.Client


trait DrsHubClient[F[_]] {
def fetchSystemStatus(): F[Boolean]
}

/*
This class represents the consumer (Cromwell) view of the DrsHub provider that implements the following endpoints:
- GET /status
*/
class DrsHubClientImpl[F[_]: Concurrent](client: Client[F], baseUrl: Uri, bearer: Token) extends DrsHubClient[F] {
override def fetchSystemStatus(): F[Boolean] = {
val request = Request[F](uri = baseUrl / "status").withHeaders(
org.http4s.headers.Accept(MediaType.application.json)
)
client.run(request).use { resp =>
resp.status match {
case Status.Ok => true.pure[F]
case Status.InternalServerError => false.pure[F]
case _ => UnknownError.raiseError
}
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package org.broadinstitute.dsde.workbench.cromwell.consumer

import au.com.dius.pact.consumer.dsl.{DslPart, PactDslResponse, PactDslWithProvider}
import org.broadinstitute.dsde.workbench.model.WorkbenchEmail
import org.http4s.Credentials.Token
import org.http4s.{AuthScheme, Credentials}
import pact4s.algebras.PactBodyJsonEncoder

case object InvalidCredentials extends Exception

case object UserAlreadyExists extends Exception

case object UnknownError extends Exception

object AuthHelper {
def mockBearerHeader(workbenchEmail: WorkbenchEmail) = s"Bearer TokenFor$workbenchEmail"
def mockAuthToken(workbenchEmail: WorkbenchEmail): Token =
Credentials.Token(AuthScheme.Bearer, s"TokenFor$workbenchEmail")
}

object PactHelper {
def buildInteraction(builder: PactDslResponse,
state: String,
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int,
responseHeaders: Seq[(String, String)],
body: DslPart
): PactDslResponse =
builder
.`given`(state)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(responseHeaders.toMap).asJava)
.body(body)

def buildInteraction(builder: PactDslResponse,
state: String,
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int
): PactDslResponse =
builder
.`given`(state)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)

def buildInteraction(builder: PactDslResponse,
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int
): PactDslResponse =
builder
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)

def buildInteraction(builder: PactDslWithProvider,
state: String,
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int,
responseHeaders: Seq[(String, String)],
body: DslPart
): PactDslResponse =
builder
.`given`(state)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(responseHeaders.toMap).asJava)
.body(body)

def buildInteraction(builder: PactDslWithProvider,
state: String,
stateParams: Map[String, Any],
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int,
responseHeaders: Seq[(String, String)],
body: DslPart
): PactDslResponse =
builder
.`given`(state, scala.jdk.CollectionConverters.MapHasAsJava(stateParams).asJava)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(responseHeaders.toMap).asJava)
.body(body)

def buildInteraction(builder: PactDslResponse,
state: String,
stateParams: Map[String, Any],
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int,
responseHeaders: Seq[(String, String)],
body: DslPart
): PactDslResponse =
builder
.`given`(state, scala.jdk.CollectionConverters.MapHasAsJava(stateParams).asJava)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(responseHeaders.toMap).asJava)
.body(body)

def buildInteraction(builder: PactDslResponse,
state: String,
stateParams: Map[String, Any],
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int,
responseHeaders: Seq[(String, String)],
): PactDslResponse =
builder
.`given`(state, scala.jdk.CollectionConverters.MapHasAsJava(stateParams).asJava)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(responseHeaders.toMap).asJava)


def buildInteraction[A](builder: PactDslWithProvider,
state: String,
stateParams: Map[String, Any],
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int,
responseHeaders: Seq[(String, String)],
body: A
)(implicit ev: PactBodyJsonEncoder[A]): PactDslResponse =
builder
.`given`(state, scala.jdk.CollectionConverters.MapHasAsJava(stateParams).asJava)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(responseHeaders.toMap).asJava)
.body(ev.toJsonString(body))

def buildInteraction[A](builder: PactDslResponse,
state: String,
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
requestBody: A,
status: Int
)(implicit ev: PactBodyJsonEncoder[A]): PactDslResponse =
builder
.`given`(state)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.body(ev.toJsonString(requestBody))
.willRespondWith()
.status(status)

def buildInteraction(builder: PactDslWithProvider,
state: String,
stateParams: Map[String, Any],
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
status: Int,
responseHeaders: Seq[(String, String)],
): PactDslResponse =
builder
.`given`(state, scala.jdk.CollectionConverters.MapHasAsJava(stateParams).asJava)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.willRespondWith()
.status(status)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(responseHeaders.toMap).asJava)


def buildInteraction[A](builder: PactDslResponse,
state: String,
stateParams: Map[String, Any],
uponReceiving: String,
method: String,
path: String,
requestHeaders: Seq[(String, String)],
requestBody: A,
status: Int
)(implicit ev: PactBodyJsonEncoder[A]): PactDslResponse =
builder
.`given`(state, scala.jdk.CollectionConverters.MapHasAsJava(stateParams).asJava)
.uponReceiving(uponReceiving)
.method(method)
.path(path)
.headers(scala.jdk.CollectionConverters.MapHasAsJava(requestHeaders.toMap).asJava)
.body(ev.toJsonString(requestBody))
.willRespondWith()
.status(status)
}
14 changes: 14 additions & 0 deletions pact4s/src/test/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<logger name="io.github.jbwheatley.pact4s.Pact4sLogger" level="INFO" />
<logger name="au.com.dius.pact.consumer" level="DEBUG"/>
<logger name="au.com.dius.pact.provider" level="DEBUG"/>

<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Loading

0 comments on commit 7a45c9e

Please sign in to comment.