Skip to content

Commit

Permalink
fix: header values need to be parsed when loaded from older spec pact…
Browse files Browse the repository at this point in the history
… files #1398
  • Loading branch information
Ronald Holshausen committed Jul 24, 2021
1 parent 8e3aa2d commit a11b05c
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ object HeaderMatcher : KLogging() {

@JvmStatic
fun parseParameters(values: List<String>): Map<String, String> {
return values.map { it.split('=').map { it.trim() } }.associate { it.first() to it.component2() }
return values.map { value ->
value.split('=').map { it.trim() }
}.associate { it.first() to it.component2() }
}

fun stripWhiteSpaceAfterCommas(str: String): String = Regex(",\\s*").replace(str, ",")
Expand Down
1 change: 1 addition & 0 deletions core/model/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.slf4j:slf4j-api:${project.slf4jVersion}"
implementation 'org.apache.tika:tika-core:1.27'
implementation 'io.ktor:ktor-http-jvm:1.3.1'

testCompile "ch.qos.logback:logback-classic:${project.logbackVersion}"
testCompile "io.github.http-builder-ng:http-builder-ng-apache:${project.httpBuilderVersion}"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package au.com.dius.pact.core.model

import au.com.dius.pact.core.support.Json
import au.com.dius.pact.core.support.json.JsonValue
import io.ktor.http.HeaderValue
import io.ktor.http.parseHeaderValue

object HeaderParser {
private val SINGLE_VALUE_HEADERS = setOf("date", "accept-datetime", "if-modified-since", "if-unmodified-since",
"expires", "retry-after")

fun fromJson(key: String, value: JsonValue): List<String> {
return when {
value is JsonValue.Array -> value.values.map { Json.toString(it).trim() }
SINGLE_VALUE_HEADERS.contains(key.toLowerCase()) -> listOf(Json.toString(value).trim())
else -> {
val sval = Json.toString(value).trim()
parseHeaderValue(sval).map { hvToString(it) }
}
}
}

private fun hvToString(headerValue: HeaderValue): String {
return if (headerValue.params.isEmpty()) {
headerValue.value.trim()
} else {
headerValue.value.trim() + ";" + headerValue.params.joinToString(";") { it.name + "=" + it.value }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,7 @@ class Request @JvmOverloads constructor(
val query = parseQueryParametersToMap(json["query"])
val headers = if (json.has("headers") && json["headers"] is JsonValue.Object) {
json["headers"].asObject().entries.entries.associate { (key, value) ->
if (value is JsonValue.Array) {
key to value.values.map { Json.toString(it) }
} else {
key to listOf(Json.toString(value).trim())
}
key to HeaderParser.fromJson(key, value)
}
} else {
emptyMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import au.com.dius.pact.core.model.generators.GeneratorTestMode
import au.com.dius.pact.core.model.generators.Generators
import au.com.dius.pact.core.model.matchingrules.MatchingRules
import au.com.dius.pact.core.model.matchingrules.MatchingRulesImpl
import au.com.dius.pact.core.support.Json
import au.com.dius.pact.core.support.json.JsonValue
import mu.KLogging

Expand Down Expand Up @@ -34,7 +33,7 @@ class Response @JvmOverloads constructor(
generators.applyGenerator(Category.HEADER, mode) { key, g ->
r.headers[key] = listOf(g.generate(context).toString())
}
r.body = generators.applyBodyGenerators(r.body, ContentType.fromString(contentType()), context, mode)
r.body = generators.applyBodyGenerators(r.body, determineContentType(), context, mode)
return r
}

Expand Down Expand Up @@ -80,11 +79,7 @@ class Response @JvmOverloads constructor(
}
val headers = if (json.has("headers") && json["headers"] is JsonValue.Object) {
json["headers"].asObject().entries.entries.associate { (key, value) ->
if (value is JsonValue.Array) {
key to value.values.map { Json.toString(it) }
} else {
key to listOf(Json.toString(value).trim())
}
key to HeaderParser.fromJson(key, value)
}
} else {
emptyMap()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package au.com.dius.pact.core.model

import au.com.dius.pact.core.support.json.JsonValue
import spock.lang.Specification
import spock.lang.Unroll

@SuppressWarnings('LineLength')
class HeaderParserSpec extends Specification {

private static final String ACCEPT =
'application/prs.hal-forms+json;q=1.0, application/hal+json;q=0.9, application/vnd.api+json;q=0.8, application/vnd.siren+json;q=0.8, application/vnd.collection+json;q=0.8, application/json;q=0.7, text/html;q=0.6, application/vnd.pactbrokerextended.v1+json;q=1.0'

@Unroll
def 'loading string headers from JSON - #desc'() {
expect:
HeaderParser.INSTANCE.fromJson(key, new JsonValue.StringValue(value)) == result

where:

desc | key | value | result
'simple header' | 'HeaderA' | 'A' | ['A']
'date header' | 'date' | 'Sat, 24 Jul 2021 04:16:53 GMT' | ['Sat, 24 Jul 2021 04:16:53 GMT']
'header with parameter' | 'content-type' | 'text/html; charset=utf-8' | ['text/html;charset=utf-8']
'header with multiple values' | 'access-control-allow-methods' | 'POST, GET, PUT, HEAD, DELETE, OPTIONS, PATCH' | ['POST', 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'PATCH']
'header with multiple values with parameters' | 'Accept' | ACCEPT | ['application/prs.hal-forms+json;q=1.0', 'application/hal+json;q=0.9', 'application/vnd.api+json;q=0.8', 'application/vnd.siren+json;q=0.8', 'application/vnd.collection+json;q=0.8', 'application/json;q=0.7', 'text/html;q=0.6', 'application/vnd.pactbrokerextended.v1+json;q=1.0']
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,44 @@ class PactReaderSpec extends Specification {
}

def 'loads a pact with V1 version using existing loader'() {
given:
def pactUrl = PactReaderSpec.classLoader.getResource('v1-pact.json')
given:
def pactUrl = PactReaderSpec.classLoader.getResource('v1-pact.json')

when:
def pact = DefaultPactReader.INSTANCE.loadPact(pactUrl)
when:
def pact = DefaultPactReader.INSTANCE.loadPact(pactUrl)
def interaction = pact.interactions.first()

then:
pact instanceof RequestResponsePact
pact.source instanceof UrlPactSource
pact.metadata == [pactSpecification: [version: '2.0.0'], 'pact-jvm': [version: '']]
then:
pact instanceof RequestResponsePact
pact.source instanceof UrlPactSource
pact.metadata == [pactSpecification: [version: '2.0.0'], 'pact-jvm': [version: '']]

interaction instanceof RequestResponseInteraction
interaction.response.headers['Content-Type'] == ['text/html']
interaction.response.headers['access-control-allow-credentials'] == ['true']
interaction.response.headers['access-control-allow-headers'] == ['Content-Type', 'Authorization']
interaction.response.headers['access-control-allow-methods'] == ['POST', 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS',
'PATCH']
}

def 'loads a pact with V2 version using existing loader'() {
given:
def pactUrl = PactReaderSpec.classLoader.getResource('v2-pact.json')
given:
def pactUrl = PactReaderSpec.classLoader.getResource('v2-pact.json')

when:
def pact = DefaultPactReader.INSTANCE.loadPact(pactUrl)
when:
def pact = DefaultPactReader.INSTANCE.loadPact(pactUrl)
def interaction = pact.interactions.first()

then:
pact instanceof RequestResponsePact
pact.metadata == [pactSpecification: [version: '2.0.0'], 'pact-jvm': [version: '']]
then:
pact instanceof RequestResponsePact
pact.metadata == [pactSpecification: [version: '2.0.0'], 'pact-jvm': [version: '']]

interaction instanceof RequestResponseInteraction
interaction.response.headers['Content-Type'] == ['text/html']
interaction.response.headers['access-control-allow-credentials'] == ['true']
interaction.response.headers['access-control-allow-headers'] == ['Content-Type', 'Authorization']
interaction.response.headers['access-control-allow-methods'] == ['POST', 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS',
'PATCH']
}

def 'loads a pact with V3 version using V3 loader'() {
Expand Down
5 changes: 4 additions & 1 deletion core/model/src/test/resources/v1-pact.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
"response": {
"status": 200,
"headers": {
"Content-Type": "text/html"
"Content-Type": "text/html",
"access-control-allow-credentials": "true",
"access-control-allow-headers": "Content-Type, Authorization",
"access-control-allow-methods": "POST, GET, PUT, HEAD, DELETE, OPTIONS, PATCH"
},
"body": "\"That is some good Mallory.\""
}
Expand Down
5 changes: 4 additions & 1 deletion core/model/src/test/resources/v2-pact.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
"response": {
"status": 200,
"headers": {
"Content-Type": "text/html"
"Content-Type": "text/html",
"access-control-allow-credentials": "true",
"access-control-allow-headers": "Content-Type, Authorization",
"access-control-allow-methods": "POST, GET, PUT, HEAD, DELETE, OPTIONS, PATCH"
},
"body": "\"That is some good Mallory.\""
}
Expand Down
2 changes: 1 addition & 1 deletion core/model/src/test/resources/v3-pact.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"response": {
"status": 200,
"headers": {
"Content-Type": "text/html"
"Content-Type": ["text/html"]
},
"body": "\"That is some good Mallory.\""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ sealed class JsonValue {

class StringValue(val value: JsonToken.StringValue) : JsonValue() {
constructor(value: CharArray) : this(JsonToken.StringValue(value))
constructor(value: String) : this(JsonToken.StringValue(value.toCharArray()))
override fun toString() = String(value.chars)
}

object True : JsonValue()
object False : JsonValue()
object Null : JsonValue()
Expand Down

0 comments on commit a11b05c

Please sign in to comment.