Skip to content

Commit

Permalink
feat: Update the new builder DSL to allow setting contents as byte ar…
Browse files Browse the repository at this point in the history
…rays #600
  • Loading branch information
rholshausen committed Apr 22, 2024
1 parent 2d86f44 commit 02a0d56
Show file tree
Hide file tree
Showing 9 changed files with 411 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,35 @@ abstract class HttpPartBuilder(private val part: IHttpPart) {
return this
}

/**
* Sets the body of the HTTP part as a byte array. If the content type is not already set, will default to
* application/octet-stream.
*/
open fun body(body: ByteArray) = body(body, null)

/**
* Sets the body of the HTTP part as a string value. If the content type is not provided or already set, will
* default to application/octet-stream.
*/
open fun body(body: ByteArray, contentTypeString: String?): HttpPartBuilder {
val contentTypeHeader = part.contentTypeHeader()
val contentType = if (!contentTypeString.isNullOrEmpty()) {
ContentType.fromString(contentTypeString)
} else if (contentTypeHeader != null) {
ContentType.fromString(contentTypeHeader)
} else {
ContentType.OCTET_STEAM
}

part.body = OptionalBody.body(body, contentType)

if (contentTypeHeader == null || contentTypeString.isNotEmpty()) {
part.headers["content-type"] = listOf(contentType.toString())
}

return this
}

/**
* Sets the body, content type and matching rules from a DslPart
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ open class HttpRequestBuilder(private val request: HttpRequest): HttpPartBuilder
return super.body(body, contentTypeString) as HttpRequestBuilder
}

override fun body(body: ByteArray): HttpRequestBuilder {
return super.body(body) as HttpRequestBuilder
}

override fun body(body: ByteArray, contentTypeString: String?): HttpRequestBuilder {
return super.body(body, contentTypeString) as HttpRequestBuilder
}

override fun body(dslPart: DslPart): HttpRequestBuilder {
return super.body(dslPart) as HttpRequestBuilder
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ open class HttpResponseBuilder(private val response: HttpResponse): HttpPartBuil
return super.body(body, contentTypeString) as HttpResponseBuilder
}

override fun body(body: ByteArray): HttpResponseBuilder {
return super.body(body) as HttpResponseBuilder
}

override fun body(body: ByteArray, contentTypeString: String?): HttpResponseBuilder {
return super.body(body, contentTypeString) as HttpResponseBuilder
}

override fun body(dslPart: DslPart): HttpResponseBuilder {
return super.body(dslPart) as HttpResponseBuilder
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@ import au.com.dius.pact.consumer.xml.PactXmlBuilder
import au.com.dius.pact.core.model.ContentType
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.generators.Category
import au.com.dius.pact.core.model.matchingrules.ContentTypeMatcher
import au.com.dius.pact.core.model.messaging.Message
import au.com.dius.pact.core.model.v4.MessageContents
import au.com.dius.pact.core.support.isNotEmpty

/**
* DSL builder for the message contents part of a V4 message
*/
class MessageContentsBuilder(var contents: MessageContents) {
fun build() = contents

/**
* Adds the expected metadata to the message contents
*/
fun withMetadata(metadata: Map<String, Any>): MessageContentsBuilder {
contents = contents.copy(metadata = metadata.mapValues { (key, value) ->
if (value is Matcher) {
contents.matchingRules.addCategory("metadata").addRule(key, value.matcher!!)
if (value.matcher != null) {
contents.matchingRules.addCategory("metadata").addRule(key, value.matcher!!)
}
if (value.generator != null) {
contents.generators.addGenerator(Category.METADATA, key, value.generator!!)
}
Expand Down Expand Up @@ -98,14 +105,57 @@ class MessageContentsBuilder(var contents: MessageContents) {
}

/**
* Adds the string as the message contents with the given content type
* Adds the string as the message contents with the given content type. If the content type is not supplied,
* it will try to detect it otherwise will default to plain text.
*/
fun withContent(payload: String, contentType: String): MessageContentsBuilder {
val ct = ContentType(contentType)
@JvmOverloads
fun withContent(payload: String, contentType: String? = null): MessageContentsBuilder {
val contentTypeMetadata = Message.contentType(contents.metadata)
val ct = if (contentType.isNotEmpty()) {
ContentType.fromString(contentType)
} else if (contentTypeMetadata.contentType != null) {
contentTypeMetadata
} else {
OptionalBody.detectContentTypeInByteArray(payload.toByteArray()) ?: ContentType.TEXT_PLAIN
}
contents = contents.copy(
contents = OptionalBody.body(payload.toByteArray(ct.asCharset()), ct),
metadata = (contents.metadata + Pair("contentType", contentType)).toMutableMap()
metadata = (contents.metadata + Pair("contentType", ct.toString())).toMutableMap()
)
return this
}

/**
* Sets the contents of the message as a byte array. If the content type is not provided or already set, will
* default to application/octet-stream.
*/
@JvmOverloads
fun withContent(payload: ByteArray, contentType: String? = null): MessageContentsBuilder {
val contentTypeMetadata = Message.contentType(contents.metadata)
val ct = if (contentType.isNotEmpty()) {
ContentType.fromString(contentType)
} else if (contentTypeMetadata.contentType != null) {
contentTypeMetadata
} else {
ContentType.OCTET_STEAM
}

contents = contents.copy(
contents = OptionalBody.body(payload, ct),
metadata = (contents.metadata + Pair("contentType", ct.toString())).toMutableMap()
)

return this
}

/**
* Sets up a content type matcher to match any payload of the given content type
*/
fun withContentsMatchingContentType(contentType: String, exampleContents: ByteArray): MessageContentsBuilder {
val ct = ContentType(contentType)
contents.contents = OptionalBody.body(exampleContents, ct)
contents.metadata["contentType"] = contentType
contents.matchingRules.addCategory("body").addRule("$", ContentTypeMatcher(contentType))
return this
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,14 @@ class HttpRequestBuilderSpec extends Specification {
}

def 'supports setting up a content type matcher on the body'() {
when:
given:
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 request = builder
.bodyMatchingContentType('image/gif', gif1px)
.build()
Expand All @@ -253,6 +255,44 @@ class HttpRequestBuilderSpec extends Specification {
)
}

def 'allows setting the body of the request as a byte array'() {
given:
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 request = builder
.body(gif1px)
.build()

then:
request.body.unwrap() == gif1px
request.body.contentType.toString() == 'application/octet-stream'
request.headers['content-type'] == ['application/octet-stream']
}

def 'allows setting the body of the request as a a byte array with a content type'() {
given:
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 request = builder
.body(gif1px, 'image/gif')
.build()

then:
request.body.unwrap() == gif1px
request.body.contentType.toString() == 'image/gif'
request.headers['content-type'] == ['image/gif']
}

def 'allows adding query parameters to the request'() {
when:
def request = builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,42 @@ class HttpResponseBuilderSpec extends Specification {
]
)
}

def 'allows setting the body of the response as a byte array'() {
given:
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 response = builder
.body(gif1px)
.build()

then:
response.body.unwrap() == gif1px
response.body.contentType.toString() == 'application/octet-stream'
response.headers['content-type'] == ['application/octet-stream']
}

def 'allows setting the body of the response as a a byte array with a content type'() {
given:
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 response = builder
.body(gif1px, 'image/gif')
.build()

then:
response.body.unwrap() == gif1px
response.body.contentType.toString() == 'image/gif'
response.headers['content-type'] == ['image/gif']
}
}
Loading

0 comments on commit 02a0d56

Please sign in to comment.