From fa6714cb2b0dd5cc21a69481a8e653a968463afa Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Tue, 26 Mar 2024 15:03:32 +1100 Subject: [PATCH] feat(consumer-dsl): Support request body as byte array #1777 --- .../consumer/dsl/PactDslRequestWithPath.kt | 13 ++++++++++ .../consumer/dsl/PactDslRequestWithoutPath.kt | 13 ++++++++++ .../dsl/PactDslRequestWithPathSpec.groovy | 24 +++++++++++++++++++ .../dsl/PactDslRequestWithoutPathSpec.groovy | 23 ++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithPath.kt b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithPath.kt index ff389da30..b78b0b6df 100644 --- a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithPath.kt +++ b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithPath.kt @@ -13,6 +13,7 @@ import au.com.dius.pact.core.model.ProviderState import au.com.dius.pact.core.model.generators.Category import au.com.dius.pact.core.model.generators.Generators import au.com.dius.pact.core.model.generators.ProviderStateGenerator +import au.com.dius.pact.core.model.matchingrules.ContentTypeMatcher import au.com.dius.pact.core.model.matchingrules.MatchingRules import au.com.dius.pact.core.model.matchingrules.RegexMatcher import au.com.dius.pact.core.model.queryStringToMap @@ -367,6 +368,18 @@ open class PactDslRequestWithPath : PactDslRequestBase { return super.bodyMatchingContentType(contentType, exampleContents) as PactDslRequestWithPath } + /** + * Request body as a binary data. It will match any expected bodies against the content type. + * @param example Example contents to use in the consumer test + * @param contentType Content type of the data + */ + fun withBinaryData(example: ByteArray, contentType: String): PactDslRequestWithPath { + requestBody = body(example, au.com.dius.pact.core.model.ContentType.fromString(contentType)) + requestHeaders["Content-Type"] = listOf(contentType) + requestMatchers.addCategory("body").addRule("$", ContentTypeMatcher(contentType)) + return this + } + /** * The path of the request * diff --git a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPath.kt b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPath.kt index 14798ef05..be1d9e2fd 100644 --- a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPath.kt +++ b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPath.kt @@ -7,6 +7,7 @@ import au.com.dius.pact.core.model.OptionalBody.Companion.body import au.com.dius.pact.core.model.PactSpecVersion import au.com.dius.pact.core.model.generators.Category import au.com.dius.pact.core.model.generators.ProviderStateGenerator +import au.com.dius.pact.core.model.matchingrules.ContentTypeMatcher import au.com.dius.pact.core.model.matchingrules.RegexMatcher import au.com.dius.pact.core.model.queryStringToMap import au.com.dius.pact.core.support.expressions.DataType @@ -288,6 +289,18 @@ open class PactDslRequestWithoutPath @JvmOverloads constructor( return super.bodyMatchingContentType(contentType, exampleContents) as PactDslRequestWithoutPath } + /** + * Request body as a binary data. It will match any expected bodies against the content type. + * @param example Example contents to use in the consumer test + * @param contentType Content type of the data + */ + fun withBinaryData(example: ByteArray, contentType: String): PactDslRequestWithoutPath { + requestBody = body(example, au.com.dius.pact.core.model.ContentType.fromString(contentType)) + requestHeaders["Content-Type"] = listOf(contentType) + requestMatchers.addCategory("body").addRule("$", ContentTypeMatcher(contentType)) + return this + } + /** * The path of the request * diff --git a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithPathSpec.groovy b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithPathSpec.groovy index bc223b090..9be9b8c4f 100644 --- a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithPathSpec.groovy +++ b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithPathSpec.groovy @@ -265,4 +265,28 @@ class PactDslRequestWithPathSpec extends Specification { def ex = thrown(au.com.dius.pact.consumer.InvalidMatcherException) ex.message == 'Example "abcd" does not match regular expression "\\d+"' } + + @Issue('#1777') + def 'supports setting binary body contents'() { + given: + def request = ConsumerPactBuilder.consumer('spec') + .hasPactWith('provider') + .uponReceiving('a PUT request with binary data') + .path('/path') + def gif1px = [ + 0107, 0111, 0106, 0070, 0067, 0141, 0001, 0000, 0001, 0000, 0200, 0000, 0000, 0377, 0377, 0377, + 0377, 0377, 0377, 0054, 0000, 0000, 0000, 0000, 0001, 0000, 0001, 0000, 0000, 0002, 0002, 0104, + 0001, 0000, 0073 + ] as byte[] + + when: + def result = request.withBinaryData(gif1px, 'image/gif') + + then: + result.requestHeaders['Content-Type'] == ['image/gif'] + result.requestBody.value == gif1px + result.requestMatchers.rulesForCategory('body').toMap(PactSpecVersion.V4) == [ + '$': [matchers: [[match: 'contentType', value: 'image/gif']], combine: 'AND'] + ] + } } diff --git a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPathSpec.groovy b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPathSpec.groovy index 6cf863910..d6e4e18be 100644 --- a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPathSpec.groovy +++ b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPathSpec.groovy @@ -93,4 +93,27 @@ class PactDslRequestWithoutPathSpec extends Specification { def ex = thrown(au.com.dius.pact.consumer.InvalidMatcherException) ex.message == 'Example "/abcd" does not match regular expression "\\/\\d+"' } + + @Issue('#1777') + def 'supports setting binary body contents'() { + given: + def request = ConsumerPactBuilder.consumer('spec') + .hasPactWith('provider') + .uponReceiving('a PUT request with binary data') + def gif1px = [ + 0107, 0111, 0106, 0070, 0067, 0141, 0001, 0000, 0001, 0000, 0200, 0000, 0000, 0377, 0377, 0377, + 0377, 0377, 0377, 0054, 0000, 0000, 0000, 0000, 0001, 0000, 0001, 0000, 0000, 0002, 0002, 0104, + 0001, 0000, 0073 + ] as byte[] + + when: + def result = request.withBinaryData(gif1px, 'image/gif') + + then: + result.requestHeaders['Content-Type'] == ['image/gif'] + result.requestBody.value == gif1px + result.requestMatchers.rulesForCategory('body').toMap(PactSpecVersion.V4) == [ + '$': [matchers: [[match: 'contentType', value: 'image/gif']], combine: 'AND'] + ] + } }