Skip to content

Commit

Permalink
fix: PactBuilder was not correctly setting up HTTP interaction given …
Browse files Browse the repository at this point in the history
…a Map structure
  • Loading branch information
uglyog committed Sep 14, 2022
1 parent 9593dec commit 1181150
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package au.com.dius.pact.consumer.dsl


import au.com.dius.pact.core.model.ContentType
import au.com.dius.pact.core.model.HttpRequest
import au.com.dius.pact.core.model.HttpResponse
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.V4Interaction
import spock.lang.Ignore
import spock.lang.Specification
import spock.lang.Unroll

Expand Down Expand Up @@ -33,4 +39,36 @@ class PactBuilderSpec extends Specification {
then:
builder.currentInteraction instanceof V4Interaction.SynchronousHttp
}

@Ignore
// This test is currently failing due to dependency linking issues
def 'supports configuring the HTTP interaction attributes'() {
given:
def builder = new PactBuilder('test', 'test', PactSpecVersion.V4)

when:
def pact = builder.expectsToReceive("test interaction", "")
.with([
'request.method': 'PUT',
'request.path': '/reports/report002.csv',
'request.query': [a: 'b'],
'request.headers': ['x-a': 'b'],
'request.contents': [
'pact:content-type': 'application/json',
'body': 'a'
],
'response.status': '200',
'response.headers': ['x-b': ['b']],
'response.contents': [
'pact:content-type': 'application/json',
'body': 'b'
]
]).toPact()
def http = pact.interactions.first().asSynchronousRequestResponse()

then:
http.request == new HttpRequest('PUT', '/reports/report002.csv', [a: ['b']], ['x-a': ['b']],
OptionalBody.body('"a"', ContentType.JSON))
http.response == new HttpResponse(205, ['x-b': ['a']], OptionalBody.body('"b"', ContentType.JSON))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ fun newHttpClient(baseUrl: String, options: Map<String, Any>): CloseableHttpClie
is Auth.BearerAuthentication -> {
builder.setDefaultHeaders(listOf(BasicHeader("Authorization", "Bearer " + auth.token)))
}
else -> {}
}
}
options["authentication"] is List<*> -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ private fun headersFromJson(json: JsonValue): Map<String, List<String>> {
data class HttpRequest @JvmOverloads constructor(
override var method: String = "GET",
override var path: String = "/",
override val query: MutableMap<String, List<String>> = mutableMapOf(),
override val headers: MutableMap<String, List<String>> = mutableMapOf(),
override var query: MutableMap<String, List<String>> = mutableMapOf(),
override var headers: MutableMap<String, List<String>> = mutableMapOf(),
override var body: OptionalBody = OptionalBody.missing(),
override val matchingRules: MatchingRules = MatchingRulesImpl(),
override val generators: Generators = Generators()
Expand Down Expand Up @@ -123,15 +123,45 @@ data class HttpRequest @JvmOverloads constructor(
}

fun updateProperties(values: Map<String, Any?>) {
logger.debug { "updateProperties(values=$values)" }
values.forEach { (key, value) ->
BeanUtils.setProperty(this, key, value)
when (key) {
"headers" -> when (value) {
is Map<*, *> -> {
headers = value
.mapKeys { it.key.toString() }
.mapValues { (_, headerValue) ->
when (headerValue) {
is List<*> -> headerValue.map { it.toString() }
else -> listOf(headerValue.toString())
}
}.toMutableMap()
}
else -> throw IllegalArgumentException("$value is not a valid value for headers")
}
"query" -> when (value) {
is Map<*, *> -> value.forEach { (name, queryValue) ->
val queryName = name.toString()
if (!query.containsKey(queryName)) {
query[queryName] = mutableListOf()
}
when (queryValue) {
is List<*> -> query[queryName] = query[queryName]!! + queryValue.map { it.toString() }
else -> query[queryName] = query[queryName]!! + queryValue.toString()
}
}
is String -> query.putAll(queryStringToMap(value.toString()))
else -> throw IllegalArgumentException("$value is not a valid value for query parameters")
}
else -> BeanUtils.setProperty(this, key, value)
}
}
}
}

data class HttpResponse @JvmOverloads constructor(
override var status: Int = 200,
override val headers: MutableMap<String, List<String>> = mutableMapOf(),
override var headers: MutableMap<String, List<String>> = mutableMapOf(),
override var body: OptionalBody = OptionalBody.missing(),
override val matchingRules: MatchingRules = MatchingRulesImpl(),
override val generators: Generators = Generators()
Expand Down Expand Up @@ -171,8 +201,24 @@ data class HttpResponse @JvmOverloads constructor(
override fun asHttpPart() = toV3Response()

fun updateProperties(values: Map<String, Any?>) {
V4Interaction.logger.debug { "updateProperties(values=$values)" }
values.forEach { (key, value) ->
BeanUtils.setProperty(this, key, value)
when (key) {
"headers" -> when (value) {
is Map<*, *> -> {
headers = value
.mapKeys { it.key.toString() }
.mapValues { (_, headerValue) ->
when (headerValue) {
is List<*> -> headerValue.map { it.toString() }
else -> listOf(headerValue.toString())
}
}.toMutableMap()
}
else -> throw IllegalArgumentException("$value is not a valid value for headers")
}
else -> BeanUtils.setProperty(this, key, value)
}
}
}

Expand Down
20 changes: 10 additions & 10 deletions core/model/src/main/kotlin/au/com/dius/pact/core/model/V4Pact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import mu.KLogging
import mu.KotlinLogging
import org.apache.commons.beanutils.BeanUtils
import org.apache.commons.lang3.builder.HashCodeBuilder
import java.util.Base64

Expand Down Expand Up @@ -213,9 +212,14 @@ sealed class V4Interaction(
}

override fun updateProperties(values: Map<String, Any?>) {
values.forEach { (key, value) ->
BeanUtils.setProperty(this, key, value)
}
val requestConfig = values.filter { it.key.startsWith("request.") }
.mapKeys { it.key.substring("request.".length) }
.toMap()
this.request.updateProperties(requestConfig)
val responseConfig = values.filter { it.key.startsWith("response.") }
.mapKeys { it.key.substring("response.".length) }
.toMap()
this.response.updateProperties(responseConfig)
}

override fun toMap(pactSpecVersion: PactSpecVersion): Map<String, *> {
Expand Down Expand Up @@ -315,9 +319,7 @@ sealed class V4Interaction(
return builder.build().toUInt().toString(16)
}

override fun updateProperties(values: Map<String, Any?>) {

}
override fun updateProperties(values: Map<String, Any?>) { }

override fun toMap(pactSpecVersion: PactSpecVersion): Map<String, *> {
val map = (mapOf(
Expand Down Expand Up @@ -459,9 +461,7 @@ sealed class V4Interaction(

override fun asV4Interaction() = this

override fun updateProperties(values: Map<String, Any?>) {

}
override fun updateProperties(values: Map<String, Any?>) { }

override fun isSynchronousMessages() = true

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package au.com.dius.pact.core.model

import spock.lang.Specification
import spock.lang.Unroll

class HttpRequestSpec extends Specification {
def 'allows configuring the interaction from properties'() {
given:
def interaction = new HttpRequest()

when:
interaction.updateProperties([
'method': 'PUT',
'path': '/reports/report002.csv',
'query': [a: 'b'],
'headers': ['x-a': 'b']
])

then:
interaction.method == 'PUT'
interaction.path == '/reports/report002.csv'
interaction.headers == ['x-a': ['b']]
interaction.query == [a: ['b']]
}

@Unroll
def 'supports setting up the query parameters'() {
given:
def interaction = new HttpRequest()

when:
interaction.updateProperties([query: queryValue])

then:
interaction.query == query

where:

queryValue | query
[a: ['b']] | [a: ['b']]
[a: 'b'] | [a: ['b']]
'a=b' | [a: ['b']]
}

@Unroll
def 'supports setting up the headers'() {
given:
def interaction = new HttpRequest()

when:
interaction.updateProperties([headers: headerValue])

then:
interaction.headers == headers

where:

headerValue | headers
[a: ['b']] | [a: ['b']]
[a: 'b'] | [a: ['b']]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package au.com.dius.pact.core.model

import spock.lang.Specification
import spock.lang.Unroll

class HttpResponseSpec extends Specification {
def 'allows configuring the interaction from properties'() {
given:
def interaction = new HttpResponse()

when:
interaction.updateProperties([
'status': '201',
'headers': ['x-a': 'b']
])

then:
interaction.status == 201
interaction.headers == ['x-a': ['b']]
}

@Unroll
def 'supports setting up the status'() {
given:
def interaction = new HttpResponse()

when:
interaction.updateProperties([status: statusValue])

then:
interaction.status == status

where:

statusValue | status
204 | 204
'203' | 203
}

@Unroll
def 'supports setting up the headers'() {
given:
def interaction = new HttpResponse()

when:
interaction.updateProperties([headers: headerValue])

then:
interaction.headers == headers

where:

headerValue | headers
[a: ['b']] | [a: ['b']]
[a: 'b'] | [a: ['b']]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package au.com.dius.pact.core.model

import spock.lang.Specification

class SynchronousHttpSpec extends Specification {
def 'allows configuring the interaction from properties'() {
given:
def interaction = new V4Interaction.SynchronousHttp('', 'test')

when:
interaction.updateProperties([
'request.method': 'PUT',
'request.path': '/reports/report002.csv',
'request.query': [a: 'b'],
'request.headers': ['x-a': 'b'],
'response.status': '205',
'response.headers': ['x-b': ['b']]
])

then:
interaction.request.method == 'PUT'
interaction.request.path == '/reports/report002.csv'
interaction.request.headers == ['x-a': ['b']]
interaction.request.query == [a: ['b']]
interaction.response.status == 205
interaction.response.headers == ['x-b': ['b']]
}
}

0 comments on commit 1181150

Please sign in to comment.