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
  • Loading branch information
Ronald Holshausen committed Jul 24, 2021
1 parent e93b1cc commit 5c92c4f
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 29 deletions.
1 change: 1 addition & 0 deletions core/model/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
implementation "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'

testImplementation "ch.qos.logback:logback-classic:${project.logbackVersion}"
testImplementation "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 @@ -149,11 +149,7 @@ class Request @Suppress("LongParameterList") @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 @@ -123,11 +122,7 @@ class Response @JvmOverloads constructor(
private fun headersFromJson(json: JsonValue.Object) =
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,6 +15,7 @@ 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)

override fun equals(other: Any?): Boolean {
Expand Down

0 comments on commit 5c92c4f

Please sign in to comment.