Skip to content

Commit

Permalink
feat: add the remaining body methods to request/response DSL builders
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Jan 17, 2023
1 parent 75ba28a commit 01dda6c
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 121 deletions.
1 change: 1 addition & 0 deletions consumer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies {
implementation 'com.github.mifmif:generex:1.0.2'
implementation 'org.apache.commons:commons-io:1.3.2'
implementation 'org.apache.commons:commons-text:1.10.0'
implementation 'org.apache.tika:tika-core'

testImplementation 'org.hamcrest:hamcrest'
testImplementation 'org.spockframework:spock-core'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package au.com.dius.pact.consumer.dsl

import au.com.dius.pact.core.model.IHttpPart
import au.com.dius.pact.core.model.ContentType
import au.com.dius.pact.core.model.ContentType.Companion.JSON
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.support.isNotEmpty
import io.ktor.http.HeaderValue
import io.ktor.http.parseHeaderValue
Expand Down Expand Up @@ -203,6 +205,66 @@ abstract class HttpPartBuilder(private val part: IHttpPart) {
return this
}

/**
* Sets the body, content type and matching rules from a DslPart
*/
open fun body(dslPart: DslPart): HttpPartBuilder {
val parent = dslPart.close()!!

part.matchingRules.addCategory(parent.matchers)
part.generators.addGenerators(parent.generators)

val contentTypeHeader = part.contentTypeHeader()
if (contentTypeHeader.isNullOrEmpty()) {
part.headers["content-type"] = listOf(JSON.toString())
part.body = OptionalBody.body(parent.toString().toByteArray())
} else {
val ct = ContentType(contentTypeHeader)
val charset = ct.asCharset()
part.body = OptionalBody.body(parent.toString().toByteArray(charset), ct)
}

return this
}

/**
* Sets the body, content type and matching rules from a BodyBuilder
*/
open fun body(builder: BodyBuilder): HttpPartBuilder {
part.matchingRules.addCategory(builder.matchers)
part.generators.addGenerators(builder.generators)

val contentTypeHeader = part.contentTypeHeader()
val contentType = builder.contentType
if (contentTypeHeader.isNullOrEmpty()) {
part.headers["content-type"] = listOf(contentType.toString())
part.body = OptionalBody.body(builder.buildBody(), contentType)
} else {
part.body = OptionalBody.body(builder.buildBody())
}

return this
}

/**
* Sets up a content type matcher to match any body of the given content type
*/
open fun bodyMatchingContentType(contentType: String, exampleContents: ByteArray): HttpPartBuilder {
val ct = ContentType(contentType)
part.body = OptionalBody.body(exampleContents, ct)
part.headers["content-type"] = listOf(contentType)
part.matchingRules.addCategory("body").addRule("$", ContentTypeMatcher(contentType))
return this
}

/**
* Sets up a content type matcher to match any body of the given content type
*/
open fun bodyMatchingContentType(contentType: String, exampleContents: String): HttpPartBuilder {
val ct = ContentType(contentType)
return bodyMatchingContentType(contentType, exampleContents.toByteArray(ct.asCharset()))
}

private fun isKnowSingleValueHeader(key: String): Boolean {
return SINGLE_VALUE_HEADERS.contains(key.lowercase())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ open class HttpRequestBuilder(private val request: HttpRequest): HttpPartBuilder
return super.body(body, contentTypeString) as HttpRequestBuilder
}

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

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

override fun bodyMatchingContentType(contentType: String, exampleContents: ByteArray): HttpRequestBuilder {
return super.bodyMatchingContentType(contentType, exampleContents) as HttpRequestBuilder
}

override fun bodyMatchingContentType(contentType: String, exampleContents: String): HttpRequestBuilder {
return super.bodyMatchingContentType(contentType, exampleContents) as HttpRequestBuilder
}

/**
* Adds a query parameter to the request. You can setup a multiple value query parameter by passing a List as the
* value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,20 @@ open class HttpResponseBuilder(private val response: HttpResponse): HttpPartBuil
override fun body(body: String, contentTypeString: String?): HttpResponseBuilder {
return super.body(body, contentTypeString) as HttpResponseBuilder
}

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

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

override fun bodyMatchingContentType(contentType: String, exampleContents: ByteArray): HttpResponseBuilder {
return super.bodyMatchingContentType(contentType, exampleContents) as HttpResponseBuilder
}

override fun bodyMatchingContentType(contentType: String, exampleContents: String): HttpResponseBuilder {
return super.bodyMatchingContentType(contentType, exampleContents) as HttpResponseBuilder
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package au.com.dius.pact.consumer.xml

import au.com.dius.pact.consumer.dsl.BodyBuilder
import au.com.dius.pact.consumer.dsl.Matcher
import au.com.dius.pact.core.model.ContentType
import au.com.dius.pact.core.model.generators.Category.BODY
import au.com.dius.pact.core.model.generators.Generators
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory
Expand Down Expand Up @@ -35,8 +37,8 @@ class PactXmlBuilder @JvmOverloads constructor (
var version: String? = null,
var charset: String? = null,
var standalone: Boolean = false
) {
val generators: Generators = Generators()
): BodyBuilder {
private val generators: Generators = Generators()
val matchingRules: MatchingRuleCategory = MatchingRuleCategory("body")

lateinit var doc: Document
Expand Down Expand Up @@ -94,6 +96,20 @@ class PactXmlBuilder @JvmOverloads constructor (

override fun toString() = String(asBytes())

override fun getMatchers() = matchingRules

override fun getGenerators() = generators

override fun getContentType(): ContentType {
return if (charset.isNullOrEmpty()) {
ContentType.XML
} else {
ContentType(org.apache.tika.mime.MediaType("application", "xml", mutableMapOf("charset" to charset)))
}
}

override fun buildBody() = asBytes(contentType.asCharset())

/**
* Sets the name of the root name
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package au.com.dius.pact.consumer.dsl

import au.com.dius.pact.consumer.xml.PactXmlBuilder
import au.com.dius.pact.core.model.HttpRequest
import au.com.dius.pact.core.model.generators.Category
import au.com.dius.pact.core.model.generators.DateGenerator
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.MatchingRuleCategory
import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.DateMatcher
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import kotlin.Pair
import spock.lang.Specification

Expand Down Expand Up @@ -191,6 +194,64 @@ class HttpRequestBuilderSpec extends Specification {
request.headers['content-type'] == ['application/json']
}

def 'supports setting the body from a DSLPart object'() {
when:
def request = builder
.body(new PactDslJsonBody().stringType('value', 'This is some text'))
.build()

then:
request.body.valueAsString() == '{"value":"This is some text"}'
request.body.contentType.toString() == 'application/json'
request.headers['content-type'] == ['application/json']
request.matchingRules.rulesForCategory('body') == new MatchingRuleCategory('body',
[
'$.value': new MatchingRuleGroup([TypeMatcher.INSTANCE])
]
)
}

def 'supports setting the body using a body builder'() {
when:
def request = builder
.body(new PactXmlBuilder('test').build {
it.attributes = [id: regexp('\\d+', '100')]
})
.build()

then:
request.body.valueAsString() == '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<test id="100"/>\n'
request.body.contentType.toString() == 'application/xml'
request.headers['content-type'] == ['application/xml']
request.matchingRules.rulesForCategory('body') == new MatchingRuleCategory('body',
[
'$.test[\'@id\']': new MatchingRuleGroup([new RegexMatcher('\\d+', '100')])
]
)
}

def 'supports setting up a content type matcher on the body'() {
when:
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[]
def request = builder
.bodyMatchingContentType('image/gif', gif1px)
.build()

then:
request.body.value == gif1px
request.body.contentType.toString() == 'image/gif'
request.headers['content-type'] == ['image/gif']
request.matchingRules.rulesForCategory('body') == new MatchingRuleCategory('body',
[
'$': new MatchingRuleGroup([new ContentTypeMatcher('image/gif')])
]
)
}

def 'allows adding query parameters to the request'() {
when:
def request = builder
Expand Down Expand Up @@ -260,123 +321,4 @@ class HttpRequestBuilderSpec extends Specification {
request.matchingRules.rulesForCategory('query') == new MatchingRuleCategory('query', [:])
request.generators.categoryFor(Category.QUERY) == [A: new ProviderStateGenerator('$a')]
}

// /**
// * The body of the request
// *
// * @param body Request body in JSON form
// */
// fun body(body: JSONObject): PactDslRequestWithoutPath {
// if (isContentTypeHeaderNotSet) {
// requestHeaders[CONTENT_TYPE] = listOf(ContentType.APPLICATION_JSON.toString())
// requestBody = body(body.toString().toByteArray())
// } else {
// val contentType = contentTypeHeader
// val ct = ContentType.parse(contentType)
// val charset = if (ct.charset != null) ct.charset else Charset.defaultCharset()
// requestBody = body(body.toString().toByteArray(charset),
// au.com.dius.pact.core.model.ContentType(contentType))
// }
// return this
// }
//
// /**
// * The body of the request
// *
// * @param body Built using the Pact body DSL
// */
// fun body(body: DslPart): PactDslRequestWithoutPath {
// val parent = body.close()
//
// requestMatchers.addCategory(parent!!.matchers)
// requestGenerators.addGenerators(parent.generators)
//
// if (isContentTypeHeaderNotSet) {
// requestHeaders[CONTENT_TYPE] = listOf(ContentType.APPLICATION_JSON.toString())
// requestBody = body(parent.toString().toByteArray())
// } else {
// val contentType = contentTypeHeader
// val ct = ContentType.parse(contentType)
// val charset = if (ct.charset != null) ct.charset else Charset.defaultCharset()
// requestBody = body(parent.toString().toByteArray(charset),
// au.com.dius.pact.core.model.ContentType(contentType))
// }
// return this
// }
//
// /**
// * The body of the request
// *
// * @param body XML Document
// */
// @Throws(TransformerException::class)
// fun body(body: Document): PactDslRequestWithoutPath {
// if (isContentTypeHeaderNotSet) {
// requestHeaders[CONTENT_TYPE] = listOf(ContentType.APPLICATION_XML.toString())
// requestBody = body(ConsumerPactBuilder.xmlToString(body).toByteArray())
// } else {
// val contentType = contentTypeHeader
// val ct = ContentType.parse(contentType)
// val charset = if (ct.charset != null) ct.charset else Charset.defaultCharset()
// requestBody = body(ConsumerPactBuilder.xmlToString(body).toByteArray(charset),
// au.com.dius.pact.core.model.ContentType(contentType))
// }
// return this
// }
//
// /**
// * XML Response body to return
// *
// * @param xmlBuilder XML Builder used to construct the XML document
// */
// fun body(xmlBuilder: PactXmlBuilder): PactDslRequestWithoutPath {
// requestMatchers.addCategory(xmlBuilder.matchingRules)
// requestGenerators.addGenerators(xmlBuilder.generators)
// if (isContentTypeHeaderNotSet) {
// requestHeaders[CONTENT_TYPE] = listOf(ContentType.APPLICATION_XML.toString())
// requestBody = body(xmlBuilder.asBytes())
// } else {
// val contentType = contentTypeHeader
// val ct = ContentType.parse(contentType)
// val charset = if (ct.charset != null) ct.charset else Charset.defaultCharset()
// requestBody = body(xmlBuilder.asBytes(charset),
// au.com.dius.pact.core.model.ContentType(contentType))
// }
// return this
// }
//
// /**
// * The body of the request
// *
// * @param body Built using MultipartEntityBuilder
// */
// open fun body(body: MultipartEntityBuilder): PactDslRequestWithoutPath {
// setupMultipart(body)
// return this
// }
//
// /**
// * Sets up a content type matcher to match any body of the given content type
// */
// public override fun bodyMatchingContentType(contentType: String, exampleContents: String)
// return super.bodyMatchingContentType(contentType, exampleContents) as PactDslRequestWithoutPath
// }
// /**
// * Sets up a file upload request. This will add the correct content type header to the request
// * @param partName This is the name of the part in the multipart body.
// * @param fileName This is the name of the file that was uploaded
// * @param fileContentType This is the content type of the uploaded file
// * @param data This is the actual file contents
// */
// @Throws(IOException::class)
// fun withFileUpload(
// partName: String,
// fileName: String,
// fileContentType: String?,
// data: ByteArray
// ): PactDslRequestWithoutPath {
// setupFileUpload(partName, fileName, fileContentType, data)
// return this
// }
//
}
Loading

0 comments on commit 01dda6c

Please sign in to comment.