Daniel Tischner
08/11/2022, 8:50 AMuponReceiving ... willRespondWith ...
part?
I noticed that some examples in the docs indent the parts to visually separate the request and the response:
builder
.given("foo")
.uponReceiving("something")
.path("ping/pong")
.method("POST")
.body("Hello")
.willRespondWith()
.successStatus()
.body("World")
.build()
But with most auto-formatting tools (IDE, Spotless, ...) this might not always be possible to achieve and it will just collapse back to:
builder
.given("foo")
.uponReceiving("something")
.path("ping/pong")
.method("POST")
.body("Hello")
.willRespondWith()
.successStatus()
.body("World")
.build()
Now, it is fairly simple to add a Lambda DSL to achieve this indents:
builder
.given("foo")
.uponReceiving("something")
.path("ping/pong") {
method("POST")
body("Hello")
}.willRespondWith()
successStatus()
body("World")
}.build()
Should I invest time into creating a PR, or am I overlooking something here?
(I noticed one issue between uponReceiving
and path
though, since they are changing types from ...WithoutPath
to ...WithPath
, so I only managed to make the DSL cut at the path
method)Daniel Tischner
08/11/2022, 8:51 AMfun PactDslRequestWithoutPath.path(
path: String,
addRequestMatchers: PactDslRequestWithPath.() -> PactDslRequestWithPath
): PactDslRequestWithPath = addRequestMatchers(path(path))
fun PactDslRequestWithoutPath.matchPath(
pathRegex: String,
pathExample: String,
addRequestMatchers: PactDslRequestWithPath.() -> PactDslRequestWithPath
): PactDslRequestWithPath = addRequestMatchers(matchPath(pathRegex, pathExample))
fun PactDslRequestWithoutPath.pathFromProviderState(
pathExpression: String,
pathExample: String,
addRequestMatchers: PactDslRequestWithPath.() -> PactDslRequestWithPath
): PactDslRequestWithPath = addRequestMatchers(pathFromProviderState(pathExpression, pathExample))
fun PactDslRequestWithPath.willRespondWith(addResponseMatchers: PactDslResponse.() -> PactDslResponse): PactDslResponse =
addResponseMatchers(willRespondWith())
uglyog
Daniel Tischner
08/11/2022, 8:53 AMuglyog
Daniel Tischner
08/11/2022, 8:59 AMYousaf Nabi (pactflow.io)
Boris
08/11/2022, 11:32 PMYousaf Nabi (pactflow.io)
Boris
08/14/2022, 7:28 AMimport au.com.dius.pact.consumer.ConsumerPactBuilder
import au.com.dius.pact.consumer.MockServer
import au.com.dius.pact.consumer.PactTestExecutionContext
import au.com.dius.pact.consumer.PactTestRun
import au.com.dius.pact.consumer.PactVerificationResult
import au.com.dius.pact.consumer.PactVerificationResult.Ok
import au.com.dius.pact.consumer.dsl.LambdaDsl
import au.com.dius.pact.consumer.dsl.LambdaDslJsonBody
import au.com.dius.pact.consumer.dsl.LambdaDslObject
import au.com.dius.pact.consumer.dsl.PactDslRequestWithPath
import au.com.dius.pact.consumer.dsl.PactDslResponse
import au.com.dius.pact.consumer.dsl.PactDslWithProvider
import au.com.dius.pact.consumer.dsl.PactDslWithState
import au.com.dius.pact.consumer.dsl.newJsonArray
import au.com.dius.pact.consumer.dsl.newObject
import au.com.dius.pact.consumer.model.MockProviderConfig
import au.com.dius.pact.consumer.runConsumerTest
import au.com.dius.pact.core.model.BasePact
import au.com.dius.pact.core.model.RequestResponsePact
import io.kotest.matchers.shouldNotBe
import retrofit2.Call
import retrofit2.Response
import java.time.ZonedDateTime
import java.util.Base64
fun pactBuilder() = ConsumerPactBuilder
.consumer("[redacted]-consumer")
.hasPactWith("[redacted]-api")
fun buildPact(
builder: PactDslWithProvider.() -> PactDslResponse
): RequestResponsePact = pactBuilder().builder().toPact()
fun buildPact(
given: String,
builder: PactDslWithState.() -> PactDslResponse
): RequestResponsePact = pactBuilder().given(given).builder().toPact()
operator fun <R> BasePact.invoke(auth: Boolean = true, createCall: RedactedClient.() -> Call<R>): R? {
println()
val result: PactVerificationResult = runConsumerTest(
this,
MockProviderConfig.createDefault(),
object : PactTestRun<Response<R>> {
override fun run(mockServer: MockServer, context: PactTestExecutionContext?): Response<R> {
val client = buildClient<RedactedClient>(mockServer.getUrl(), auth)
return client.createCall().execute()
}
}
)
println("testPact result: $result")
return ((result as? Ok)?.result as? Response<R>?)?.body()
}
fun PactDslRequestWithPath.jsonHeader() =
matchHeader("Content-Type", "^application/json.*$", "application/json; charset utf-8")
fun PactDslRequestWithPath.authHeader(user: String = "admin", pass: String = "admin") =
matchHeader("Authorization", "^Basic .*$", "Basic ${"$user:$pass".base64}")
val String.base64: String
get() = Base64.getEncoder().encodeToString(encodeToByteArray())
fun oneOf(vararg values: String): String = values.joinToString("|")
fun LambdaDslObject.zonedDateTime(
fieldName: String,
example: String = "2021-04-28T00:32:16.450595Z"
): LambdaDslObject = datetime(
fieldName,
"YYYY-MM-dd'T'HH:mm:ss.SSSSSSXXX",
ZonedDateTime.parse(example)
)
fun LambdaDslJsonBody.arrayLike(fieldName: String, eachBuilder: LambdaDslObject.() -> Unit): LambdaDslObject =
minArrayLike(fieldName, 1) { eachBuilder(it) }
infix fun PactDslRequestWithPath.jsonBody(bodyBuilder: LambdaDslJsonBody.() -> Unit): PactDslRequestWithPath =
body(LambdaDsl.newJsonBody(bodyBuilder).build())
infix fun PactDslResponse.jsonBody(bodyBuilder: LambdaDslJsonBody.() -> Unit): PactDslResponse =
body(LambdaDsl.newJsonBody(bodyBuilder).build())
fun PactDslResponse.jsonArray(times: Int = 1, body: LambdaDslObject.() -> Unit): PactDslResponse =
body(newJsonArray { repeat(times) { newObject(body).build() } })
fun PactDslResponse.jsonArray(vararg body: LambdaDslObject.() -> Unit): PactDslResponse =
body(newJsonArray { body.map { newObject(it).build() } })
infix fun <T> Iterable<T>.shouldContainMatch(findMatch: T.()->Boolean) =
find(findMatch) shouldNotBe null
Daniel Tischner
08/19/2022, 1:23 PMBoris
08/22/2022, 5:56 AMDaniel Tischner
08/22/2022, 9:30 AMarrayLike
, will use that in my own code base until u add it to pact directly. thanksBoris
08/22/2022, 11:30 PMBoris
08/22/2022, 11:32 PMscenario("incomplete") {
val pact = buildPact(given = "transaction in progress") {
uponReceiving("a request to check the status of a running transaction").run {
method("GET").matchPath(
"/admin/amendment-batch/${RegexUUID}",
"/admin/amendment-batch/${ConstUUID}"
).authHeader()
}.willRespondWith().run {
status(200) jsonBody {
zonedDateTime("startDate")
array("amendments") { a ->
a.`object` {
it.uuid("id", ConsistentUUID)
it.zonedDateTime("startDate")
it.zonedDateTime("endDate")
it.stringValue("status", "Success")
}
a.`object` {
it.uuid("id", ConsistentUUID)
it.zonedDateTime("startDate")
it.stringValue("status", "InProgress")
}
a.`object` {
it.uuid("id", ConsistentUUID)
it.stringValue("status", "NotStarted")
}
}
}
}
}
val response = pact {
checkPublishAmendmentStatus(UUID.randomUUID().toString())
}
println("In-progress transaction response: $response")
response!!.apply {
amendments shouldContainMatch { status == "Success" }
amendments shouldContainMatch { status == "InProgress" }
amendments shouldContainMatch { status == "NotStarted" }
}
}
Boris
08/22/2022, 11:35 PMscenario("published only") {
val pact = buildPact(given = "several published amendments") {
uponReceiving("a request to list admin amendments").run {
method("GET").path("/admin/amendments")
.query("unpublished=false")
.authHeader()
}.willRespondWith().run {
status(200).jsonArray(3) {
uuid("amendmentId", ConsistentUUID)
stringType("displayId", "VC999")
booleanValue("unpublished", false)
}
}
}
val response = pact {
listAdminAmendments(unpublished = false)
}
response!!.forEach {
it.amendmentId shouldBe ConsistentUUID
it.displayId shouldBe "VC999"
it.unpublished shouldBe false
}
}
Boris
08/22/2022, 11:36 PMwith
, apply
, and run
Daniel Tischner
08/23/2022, 6:53 AMBoris
08/23/2022, 6:54 AM