diff --git a/consumer/groovy/README.md b/consumer/groovy/README.md index 172ad25663..93afd26b14 100644 --- a/consumer/groovy/README.md +++ b/consumer/groovy/README.md @@ -541,7 +541,7 @@ For example: By default, bodies will be handled based on their content types. For binary contents, the bodies will be base64 encoded when written to the Pact file and then decoded again when the file is loaded. You can change this with -an override property: `pact.content_type.override..=text|binary`. For instance, setting +an override property: `pact.content_type.override..=text|json|binary`. For instance, setting `pact.content_type.override.application.pdf=text` will treat PDF bodies as a text type and not encode/decode them. ## Changing the directory pact files are written to diff --git a/consumer/junit/README.md b/consumer/junit/README.md index 0d980e242a..a7c27e2c9c 100644 --- a/consumer/junit/README.md +++ b/consumer/junit/README.md @@ -580,7 +580,7 @@ The `and` and `or` methods take a variable number of matchers (varargs). By default, bodies will be handled based on their content types. For binary contents, the bodies will be base64 encoded when written to the Pact file and then decoded again when the file is loaded. You can change this with -an override property: `pact.content_type.override..=text|binary`. For instance, setting +an override property: `pact.content_type.override..=text|binary|json`. For instance, setting `pact.content_type.override.application.pdf=text` will treat PDF bodies as a text type and not encode/decode them. ### Matching on paths diff --git a/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/MatchingConfig.kt b/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/MatchingConfig.kt index 5ba8e4dfe2..91cd780e3e 100644 --- a/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/MatchingConfig.kt +++ b/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/MatchingConfig.kt @@ -21,17 +21,10 @@ object MatchingConfig { val clazz = Class.forName(matcher).kotlin (clazz.objectInstance ?: clazz.createInstance()) as BodyMatcher? } else { - val override = System.getProperty("pact.content_type.override.$contentType") - if (override != null) { - val matcherOverride = bodyMatchers.entries.find { override.matches(Regex(it.key)) }?.value - if (matcherOverride != null) { - val clazz = Class.forName(matcherOverride).kotlin - (clazz.objectInstance ?: clazz.createInstance()) as BodyMatcher? - } else { - null - } - } else { - null + when (System.getProperty("pact.content_type.override.$contentType")) { + "json" -> JsonBodyMatcher + "text" -> PlainTextBodyMatcher() + else -> null } } } else { diff --git a/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/MatchingConfigSpec.groovy b/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/MatchingConfigSpec.groovy index 0c0c72ea81..77d09a9bbd 100644 --- a/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/MatchingConfigSpec.groovy +++ b/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/MatchingConfigSpec.groovy @@ -2,11 +2,14 @@ package au.com.dius.pact.core.matchers import spock.lang.Specification import spock.lang.Unroll +import spock.util.environment.RestoreSystemProperties +@RestoreSystemProperties class MatchingConfigSpec extends Specification { def setupSpec() { System.setProperty('pact.content_type.override.application/x-thrift', 'json') + System.setProperty('pact.content_type.override.application/x-other', 'text') } @Unroll @@ -24,6 +27,6 @@ class MatchingConfigSpec extends Specification { 'application/json-rpc' | 'au.com.dius.pact.core.matchers.JsonBodyMatcher' 'application/jsonrequest' | 'au.com.dius.pact.core.matchers.JsonBodyMatcher' 'application/x-thrift' | 'au.com.dius.pact.core.matchers.JsonBodyMatcher' + 'application/x-other' | 'au.com.dius.pact.core.matchers.PlainTextBodyMatcher' } - } diff --git a/core/model/src/main/kotlin/au/com/dius/pact/core/model/ContentType.kt b/core/model/src/main/kotlin/au/com/dius/pact/core/model/ContentType.kt index 3d71545e24..21e347672b 100644 --- a/core/model/src/main/kotlin/au/com/dius/pact/core/model/ContentType.kt +++ b/core/model/src/main/kotlin/au/com/dius/pact/core/model/ContentType.kt @@ -16,15 +16,19 @@ class ContentType(val contentType: MediaType?) { fun isJson(): Boolean { return if (contentType != null) { - val override = System.getProperty("pact.content_type.override.${contentType.baseType}") - if (override == null) - jsonRegex.matches(contentType.subtype.toLowerCase()) - else - jsonRegex.matches(override) + when (System.getProperty("pact.content_type.override.${contentType.baseType}")) { + "json" -> true + else -> jsonRegex.matches(contentType.subtype.toLowerCase()) + } } else false } - fun isXml(): Boolean = if (contentType != null) xmlRegex.matches(contentType.subtype.toLowerCase()) else false + fun isXml(): Boolean = if (contentType != null) { + when (System.getProperty("pact.content_type.override.${contentType.baseType}")) { + "xml" -> true + else -> xmlRegex.matches(contentType.subtype.toLowerCase()) + } + } else false fun isOctetStream(): Boolean = if (contentType != null) contentType.baseType.toString() == "application/octet-stream" @@ -62,6 +66,7 @@ class ContentType(val contentType: MediaType?) { val type = contentType.type val baseType = superType.type val override = System.getProperty("pact.content_type.override.$type.${contentType.subtype}") + ?: System.getProperty("pact.content_type.override.$type/${contentType.subtype}") when { override.isNotEmpty() -> override == "binary" type == "text" || baseType == "text" -> false diff --git a/core/model/src/main/kotlin/au/com/dius/pact/core/model/HttpPart.kt b/core/model/src/main/kotlin/au/com/dius/pact/core/model/HttpPart.kt index 6b99705fe0..10ed1274e0 100644 --- a/core/model/src/main/kotlin/au/com/dius/pact/core/model/HttpPart.kt +++ b/core/model/src/main/kotlin/au/com/dius/pact/core/model/HttpPart.kt @@ -79,18 +79,23 @@ abstract class HttpPart { private const val CONTENT_TYPE = "Content-Type" @JvmStatic - fun extractBody(json: JsonValue.Object, contentType: ContentType): OptionalBody { + @JvmOverloads + fun extractBody( + json: JsonValue.Object, + contentType: ContentType, + decoder: Base64.Decoder = Base64.getDecoder() + ): OptionalBody { return when (val b = json["body"]) { is JsonValue.Null -> OptionalBody.nullBody() - is JsonValue.StringValue -> decodeBody(b.asString()!!, contentType) - else -> decodeBody(b.serialise(), contentType) + is JsonValue.StringValue -> decodeBody(b.toString(), contentType, decoder) + else -> decodeBody(b.serialise(), contentType, decoder) } } - private fun decodeBody(body: String, contentType: ContentType): OptionalBody { + private fun decodeBody(body: String, contentType: ContentType, decoder: Base64.Decoder): OptionalBody { return when { contentType.isBinaryType() || contentType.isMultipart() -> try { - OptionalBody.body(Base64.getDecoder().decode(body), contentType) + OptionalBody.body(decoder.decode(body), contentType) } catch (ex: IllegalArgumentException) { logger.warn(ex) { "Expected body for content type $contentType to be base64 encoded" } OptionalBody.body(body.toByteArray(contentType.asCharset()), contentType) diff --git a/core/model/src/test/groovy/au/com/dius/pact/core/model/ContentTypeSpec.groovy b/core/model/src/test/groovy/au/com/dius/pact/core/model/ContentTypeSpec.groovy index ee595fc88d..05dc134328 100644 --- a/core/model/src/test/groovy/au/com/dius/pact/core/model/ContentTypeSpec.groovy +++ b/core/model/src/test/groovy/au/com/dius/pact/core/model/ContentTypeSpec.groovy @@ -2,20 +2,25 @@ package au.com.dius.pact.core.model import spock.lang.Specification import spock.lang.Unroll +import spock.util.environment.RestoreSystemProperties import java.nio.charset.Charset @SuppressWarnings('UnnecessaryBooleanExpression') +@RestoreSystemProperties class ContentTypeSpec extends Specification { def setupSpec() { System.setProperty('pact.content_type.override.application/x-thrift', 'json') + System.setProperty('pact.content_type.override.application/x-other', 'text') + System.setProperty('pact.content_type.override.application/x-bin', 'binary') + System.setProperty('pact.content_type.override.application/x-ml', 'xml') } @Unroll def '"#value" is json -> #result'() { expect: - result ? contentType.json : !contentType.json + result == contentType.json where: @@ -27,6 +32,7 @@ class ContentTypeSpec extends Specification { 'application/hal+json' || true 'application/HAL+JSON' || true 'application/x-thrift' || true + 'application/x-other' || false contentType = new ContentType(value) } @@ -34,7 +40,7 @@ class ContentTypeSpec extends Specification { @Unroll def '"#value" is xml -> #result'() { expect: - result ? contentType.xml : !contentType.xml + result == contentType.xml where: @@ -45,6 +51,8 @@ class ContentTypeSpec extends Specification { 'application/xml' || true 'application/stuff+xml' || true 'application/STUFF+XML' || true + 'application/x-ml' || true + 'application/x-thrift' || false contentType = new ContentType(value) } @@ -89,6 +97,7 @@ class ContentTypeSpec extends Specification { 'text/csv' || false 'multipart/form-data' || true 'application/x-www-form-urlencoded' || false + 'application/x-bin' || true contentType = new ContentType(value) } diff --git a/core/model/src/test/groovy/au/com/dius/pact/core/model/HttpPartSpec.groovy b/core/model/src/test/groovy/au/com/dius/pact/core/model/HttpPartSpec.groovy index 3a7b6eca8b..eeaa99e230 100644 --- a/core/model/src/test/groovy/au/com/dius/pact/core/model/HttpPartSpec.groovy +++ b/core/model/src/test/groovy/au/com/dius/pact/core/model/HttpPartSpec.groovy @@ -1,8 +1,10 @@ package au.com.dius.pact.core.model import au.com.dius.pact.core.support.json.JsonValue +import spock.lang.Issue import spock.lang.Specification import spock.lang.Unroll +import spock.util.environment.RestoreSystemProperties import java.nio.charset.Charset @@ -61,4 +63,20 @@ class HttpPartSpec extends Specification { HttpPart.extractBody(json, ContentType.fromString('application/zip')) .valueAsString() == 'hello' } + + @Issue('#1314') + @RestoreSystemProperties + def 'takes into account content type overrides'() { + given: + def json = new JsonValue.Object([body: new JsonValue.StringValue('{}'.chars)]) + System.setProperty('pact.content_type.override.application/x-thrift', 'json') + def decoder = Mock(Base64.Decoder) + + when: + def result = HttpPart.extractBody(json, ContentType.fromString('application/x-thrift'), decoder) + + then: + 0 * decoder.decode(_) + result.valueAsString() == '{}' + } } diff --git a/provider/gradle/README.md b/provider/gradle/README.md index 63a4539c3f..7c9338b50e 100644 --- a/provider/gradle/README.md +++ b/provider/gradle/README.md @@ -98,7 +98,7 @@ The following project properties can be specified with `-Pproperty=value` on the |`pact.verifier.disableUrlPathDecoding`|Disables decoding of request paths| |`pact.pactbroker.httpclient.usePreemptiveAuthentication`|Enables preemptive authentication with the pact broker when set to `true`| |`pact.provider.tag`|Sets the provider tag to push before publishing verification results (can use a comma separated list)| -|`pact.content_type.override..=` where `` may be `text` or `binary`|Overrides the handling of a particular content type [4.1.3+]| +|`pact.content_type.override..=` where `` may be `text`, `json` or `binary`|Overrides the handling of a particular content type [4.1.3+]| ## Specifying the provider hostname at runtime diff --git a/provider/junit/README.md b/provider/junit/README.md index f1d6ff1171..cbecf73b38 100644 --- a/provider/junit/README.md +++ b/provider/junit/README.md @@ -451,7 +451,7 @@ having been verified due to a configuration error. ** By default, bodies will be handled based on their content types. For binary contents, the bodies will be base64 encoded when written to the Pact file and then decoded again when the file is loaded. You can change this with -an override property: `pact.content_type.override..=text|binary`. For instance, setting +an override property: `pact.content_type.override..=text|json|binary`. For instance, setting `pact.content_type.override.application.pdf=text` will treat PDF bodies as a text type and not encode/decode them. ## Test target diff --git a/provider/junit5/README.md b/provider/junit5/README.md index 5587a5e07c..09ae2d182c 100644 --- a/provider/junit5/README.md +++ b/provider/junit5/README.md @@ -169,7 +169,7 @@ null values for any of the injected parameters. By default, bodies will be handled based on their content types. For binary contents, the bodies will be base64 encoded when written to the Pact file and then decoded again when the file is loaded. You can change this with -an override property: `pact.content_type.override..=text|binary`. For instance, setting +an override property: `pact.content_type.override..=text|json|binary`. For instance, setting `pact.content_type.override.application.pdf=text` will treat PDF bodies as a text type and not encode/decode them. # Pending Pact Support (version 4.1.0 and later) diff --git a/provider/maven/README.md b/provider/maven/README.md index 1ff423c9c6..2eb51e5340 100644 --- a/provider/maven/README.md +++ b/provider/maven/README.md @@ -265,7 +265,7 @@ The following plugin properties can be specified with `-Dproperty=value` on the |`pact.verifier.disableUrlPathDecoding`|Disables decoding of request paths| |`pact.pactbroker.httpclient.usePreemptiveAuthentication`|Enables preemptive authentication with the pact broker when set to `true`| |`pact.consumer.tags`|Overrides the tags used when publishing pacts [version 4.0.7+]| -|`pact.content_type.override..=text\|binary`|Overrides the handling of a particular content type [version 4.1.3+]| +|`pact.content_type.override..=text\|json\|binary`|Overrides the handling of a particular content type [version 4.1.3+]| Example in the configuration section: @@ -751,7 +751,7 @@ For example: By default, bodies will be handled based on their content types. For binary contents, the bodies will be base64 encoded when written to the Pact file and then decoded again when the file is loaded. You can change this with -an override property: `pact.content_type.override..=text|binary`. For instance, setting +an override property: `pact.content_type.override..=text|json|binary`. For instance, setting `pact.content_type.override.application.pdf=text` will treat PDF bodies as a text type and not encode/decode them. # Publishing verification results to a Pact Broker