Skip to content

Commit

Permalink
feat: Add support for matching rules with headers with new DSL
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Jan 16, 2023
1 parent f44989b commit b888d07
Show file tree
Hide file tree
Showing 6 changed files with 656 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ 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.OptionalBody
import au.com.dius.pact.core.model.generators.Category
import au.com.dius.pact.core.support.isNotEmpty
import io.ktor.http.HeaderValue
import io.ktor.http.parseHeaderValue

open class HttpPartBuilder(private val part: IHttpPart) {
abstract class HttpPartBuilder(private val part: IHttpPart) {

/**
* Adds a header to the HTTP part. The value will be converted to a string (using the toString() method), unless it
Expand All @@ -18,7 +19,28 @@ open class HttpPartBuilder(private val part: IHttpPart) {
*/
open fun header(key: String, value: Any): HttpPartBuilder {
val headValues = when (value) {
is List<*> -> value.map { it.toString() }
is List<*> -> value.mapIndexed { index, v ->
if (v is Matcher) {
if (v.matcher != null) {
part.matchingRules.addCategory("header").addRule("$key[$index]", v.matcher!!)
}
if (v.generator != null) {
part.generators.addGenerator(Category.HEADER, "$key[$index]", v.generator!!)
}
v.value.toString()
} else {
v.toString()
}
}
is Matcher -> {
if (value.matcher != null) {
part.matchingRules.addCategory("header").addRule(key, value.matcher!!)
}
if (value.generator != null) {
part.generators.addGenerator(Category.HEADER, key, value.generator!!)
}
listOf(value.value.toString())
}
else -> if (isKnowSingleValueHeader(key)) {
listOf(value.toString())
} else {
Expand Down Expand Up @@ -73,13 +95,22 @@ open class HttpPartBuilder(private val part: IHttpPart) {
* For example: `headers("OPTIONS", "GET, POST, PUT")` is the same as
* `header("OPTIONS", List.of("GET", "POST, "PUT"))`
*/
open fun headers(nameValuePairs: Array<out Pair<String, String>>): HttpPartBuilder {
open fun headers(nameValuePairs: Array<out Pair<String, Any>>): HttpPartBuilder {
val headersMap = nameValuePairs.toList().fold(mutableMapOf<String, MutableList<String>>()) { acc, value ->
val k = value.first
val v = if (isKnowSingleValueHeader(k)) {
listOf(value.second)
val v = if (value.second is Matcher) {
val matcher = value.second as Matcher
if (matcher.matcher != null) {
part.matchingRules.addCategory("header").addRule(k, matcher.matcher!!)
}
if (matcher.generator != null) {
part.generators.addGenerator(Category.HEADER, k, matcher.generator!!)
}
listOf(matcher.value.toString())
} else if (isKnowSingleValueHeader(k)) {
listOf(value.second.toString())
} else {
parseHeaderValue(value.second).map { headerToString(it) }
parseHeaderValue(value.second.toString()).map { headerToString(it) }
}
if (acc.containsKey(k)) {
acc[k]!!.addAll(v)
Expand Down Expand Up @@ -107,10 +138,31 @@ open class HttpPartBuilder(private val part: IHttpPart) {
open fun headers(values: Map<String, Any>): HttpPartBuilder {
val headersMap = values.mapValues { entry ->
val k = entry.key
if (isKnowSingleValueHeader(k)) {
listOf(entry.value.toString())
if (entry.value is Matcher) {
val matcher = entry.value as Matcher
if (matcher.matcher != null) {
part.matchingRules.addCategory("header").addRule(k, matcher.matcher!!)
}
if (matcher.generator != null) {
part.generators.addGenerator(Category.HEADER, k, matcher.generator!!)
}
listOf(matcher.value.toString())
} else if (entry.value is List<*>) {
(entry.value as List<*>).map { it.toString() }
(entry.value as List<*>).mapIndexed { index, v ->
if (v is Matcher) {
if (v.matcher != null) {
part.matchingRules.addCategory("header").addRule("$k[$index]", v.matcher!!)
}
if (v.generator != null) {
part.generators.addGenerator(Category.HEADER, "$k[$index]", v.generator!!)
}
v.value.toString()
} else {
v.toString()
}
}
} else if (isKnowSingleValueHeader(k)) {
listOf(entry.value.toString())
} else {
parseHeaderValue(entry.value.toString()).map { headerToString(it) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package au.com.dius.pact.consumer.dsl

import au.com.dius.pact.core.model.HttpRequest
import au.com.dius.pact.core.model.generators.Category

/**
* Pact HTTP Request builder DSL that supports V4 formatted Pact files
Expand Down Expand Up @@ -29,6 +30,24 @@ open class HttpRequestBuilder(private val request: HttpRequest): HttpPartBuilder
return this
}

/**
* Sets the path of the request using a matching rule. For example:
*
* ```
* path(regexp("\\/path\\/\\d+", "/path/1000"))
* ```
*/
fun path(matcher: Matcher): HttpRequestBuilder {
if (matcher.matcher != null) {
request.matchingRules.addCategory("path").addRule(matcher.matcher!!)
}
if (matcher.generator != null) {
request.generators.addGenerator(Category.PATH, "", matcher.generator!!)
}
request.path = matcher.value.toString()
return this
}

override fun header(key: String, value: Any): HttpRequestBuilder {
return super.header(key, value) as HttpRequestBuilder
}
Expand All @@ -37,7 +56,7 @@ open class HttpRequestBuilder(private val request: HttpRequest): HttpPartBuilder
return super.headers(key, value, nameValuePairs) as HttpRequestBuilder
}

override fun headers(vararg nameValuePairs: Pair<String, String>): HttpRequestBuilder {
override fun headers(vararg nameValuePairs: Pair<String, Any>): HttpRequestBuilder {
return super.headers(nameValuePairs) as HttpRequestBuilder
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ open class HttpResponseBuilder(private val response: HttpResponse): HttpPartBuil
return super.headers(key, value, nameValuePairs) as HttpResponseBuilder
}

override fun headers(vararg nameValuePairs: Pair<String, String>): HttpResponseBuilder {
override fun headers(vararg nameValuePairs: Pair<String, Any>): HttpResponseBuilder {
return super.headers(nameValuePairs) as HttpResponseBuilder
}

Expand Down
14 changes: 14 additions & 0 deletions consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/Matchers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package au.com.dius.pact.consumer.dsl
import au.com.dius.pact.core.model.generators.DateGenerator
import au.com.dius.pact.core.model.generators.DateTimeGenerator
import au.com.dius.pact.core.model.generators.Generator
import au.com.dius.pact.core.model.generators.ProviderStateGenerator
import au.com.dius.pact.core.model.generators.RandomBooleanGenerator
import au.com.dius.pact.core.model.generators.RandomDecimalGenerator
import au.com.dius.pact.core.model.generators.RandomHexadecimalGenerator
Expand Down Expand Up @@ -80,6 +81,9 @@ data class IncludeMatcher(override val value: String) : Matcher(value,

object NullMatcher : Matcher(null, au.com.dius.pact.core.model.matchingrules.NullMatcher)

data class ProviderStateMatcher(val expression: String, override val value: String) : Matcher(value,
null, ProviderStateGenerator(expression))

/**
* Exception for handling invalid matchers
*/
Expand Down Expand Up @@ -298,4 +302,14 @@ object Matchers {
fun nullValue(): Matcher {
return NullMatcher
}

/**
* Value injected from a provider state
* @param expression Expression to use to match a value in the provider state.
* @param value Example value, if not provided the current date will be used
*/
@JvmStatic
fun fromProviderState(expression: String, value: String): Matcher {
return ProviderStateMatcher(expression, value)
}
}
Loading

0 comments on commit b888d07

Please sign in to comment.