Skip to content

Commit

Permalink
fix: Trim regex anchors before generating random strings from the regex
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Oct 23, 2024
1 parent 0554c1a commit ec066a0
Show file tree
Hide file tree
Showing 19 changed files with 132 additions and 26 deletions.
2 changes: 0 additions & 2 deletions consumer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ dependencies {

implementation 'org.apache.httpcomponents.client5:httpclient5-fluent'
implementation 'com.googlecode.java-diff-utils:diffutils:1.3.0'
implementation 'dk.brics.automaton:automaton:1.11-8'
implementation('io.netty:netty-handler') {
exclude module: 'netty-transport-native-kqueue'
}
Expand All @@ -26,7 +25,6 @@ dependencies {
exclude group: 'au.com.dius.pact.core'
}
implementation 'org.apache.commons:commons-lang3'
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'
Expand Down
1 change: 0 additions & 1 deletion consumer/groovy/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ dependencies {
implementation 'org.apache.groovy:groovy'
implementation 'org.apache.groovy:groovy-json'
implementation 'org.apache.httpcomponents.client5:httpclient5'
implementation 'com.github.mifmif:generex:1.0.2'
implementation 'org.apache.commons:commons-lang3'
implementation 'org.apache.commons:commons-collections4'
implementation('io.pact.plugin.driver:core') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.support.Random
import au.com.dius.pact.core.support.isNotEmpty
import com.mifmif.common.regex.Generex
import io.github.oshai.kotlinlogging.KLogging
import org.apache.commons.lang3.time.DateFormatUtils
import org.apache.commons.lang3.time.DateUtils
Expand Down Expand Up @@ -69,7 +69,7 @@ class RegexpMatcher @JvmOverloads constructor(
value: String? = null
) : Matcher(value, RegexMatcher(regex, value), if (value == null) RegexGenerator(regex) else null) {
override val value: Any?
get() = super.value ?: Generex(regex).random()
get() = super.value ?: Random.generateRandomString(regex)
}

class HexadecimalMatcher @JvmOverloads constructor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package au.com.dius.pact.consumer.groovy

import spock.lang.Issue
import spock.lang.Specification

class RegexpMatcherSpec extends Specification {
def 'returns the value provided to the constructor'() {
expect:
new RegexpMatcher('\\w+', 'word').value == 'word'
}

def 'if no value is provided to the constructor, generates a random value when needed'() {
expect:
new RegexpMatcher('\\w+', null).value ==~ /\w+/
}

@Issue('#1826')
def 'handles regex anchors'() {
expect:
new RegexpMatcher('^\\w+$', null).value ==~ /\w+/
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.support.Json
import com.mifmif.common.regex.Generex
import org.json.JSONArray
import au.com.dius.pact.core.support.Random

class ArrayOfPrimitivesBuilder {

Expand Down Expand Up @@ -62,7 +61,7 @@ class ArrayOfPrimitivesBuilder {
fun thatMatchRegex(regex: String): ArrayOfPrimitivesBuilder {
this.matcher = RegexMatcher(regex)
this.generator = RegexGenerator(regex)
this.value = Generex(regex).random()
this.value = Random.generateRandomString(regex)
return this
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import au.com.dius.pact.core.model.generators.RegexGenerator
import au.com.dius.pact.core.model.generators.TimeGenerator
import au.com.dius.pact.core.model.generators.UuidGenerator
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory
import au.com.dius.pact.core.support.Random
import au.com.dius.pact.core.support.expressions.DataType
import com.mifmif.common.regex.Generex
import org.apache.commons.lang3.time.DateFormatUtils
import org.apache.commons.lang3.time.FastDateFormat
import java.net.URLEncoder
Expand Down Expand Up @@ -100,7 +100,7 @@ class FormPostBuilder(
*/
fun stringMatcher(name: String, regex: String): FormPostBuilder {
generators.addGenerator(Category.BODY, name, RegexGenerator(regex))
return stringMatcher(name, regex, Generex(regex).random())
return stringMatcher(name, regex, Random.generateRandomString(regex))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import au.com.dius.pact.core.model.matchingrules.ValuesMatcher
import au.com.dius.pact.core.model.matchingrules.expressions.MatchingRuleDefinition
import au.com.dius.pact.core.support.Json.toJson
import au.com.dius.pact.core.support.Random
import au.com.dius.pact.core.support.expressions.DataType.Companion.from
import au.com.dius.pact.core.support.json.JsonValue
import au.com.dius.pact.core.support.padTo
import com.mifmif.common.regex.Generex
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.time.DateFormatUtils
import org.apache.commons.lang3.time.FastDateFormat
Expand Down Expand Up @@ -795,7 +795,7 @@ open class PactDslJsonBody : DslPart {
*/
fun stringMatcher(name: String, regex: String): PactDslJsonBody {
generators.addGenerator(Category.BODY, matcherKey(name, rootPath), RegexGenerator(regex))
stringMatcher(name, regex, *examples(Generex(regex).random()))
stringMatcher(name, regex, *examples(Random.generateRandomString(regex)))
return this
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.RuleLogic
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import au.com.dius.pact.core.support.Json.toJson
import au.com.dius.pact.core.support.Random
import au.com.dius.pact.core.support.expressions.DataType.Companion.from
import au.com.dius.pact.core.support.json.JsonValue
import com.mifmif.common.regex.Generex
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.time.DateFormatUtils
import org.apache.commons.lang3.time.FastDateFormat
Expand Down Expand Up @@ -612,7 +612,7 @@ open class PactDslJsonRootValue : DslPart("", "") {
fun stringMatcher(regex: String): PactDslJsonRootValue {
val rootValue = PactDslJsonRootValue()
rootValue.generators.addGenerator(Category.BODY, "", RegexGenerator(regex))
rootValue.value = Generex(regex).random()
rootValue.value = Random.generateRandomString(regex)
rootValue.setMatcher(rootValue.regexp(regex))
return rootValue
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ 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
import au.com.dius.pact.core.support.Random
import au.com.dius.pact.core.support.expressions.DataType
import au.com.dius.pact.core.support.json.JsonValue
import com.mifmif.common.regex.Generex
import org.apache.commons.lang3.time.DateFormatUtils
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder
import org.apache.hc.core5.http.ContentType
Expand Down Expand Up @@ -397,7 +397,7 @@ open class PactDslRequestWithPath : PactDslRequestBase {
* @param pathRegex regular expression to use to match paths
*/
@JvmOverloads
fun matchPath(pathRegex: String, path: String = Generex(pathRegex).random()): PactDslRequestWithPath {
fun matchPath(pathRegex: String, path: String = Random.generateRandomString(pathRegex)): PactDslRequestWithPath {
val re = Regex(pathRegex)
if (!path.matches(re)) {
throw InvalidMatcherException("Example \"$path\" does not match regular expression \"$pathRegex\"")
Expand All @@ -420,7 +420,7 @@ open class PactDslRequestWithPath : PactDslRequestBase {
fun matchHeader(
header: String,
regex: String,
headerExample: String = Generex(regex).random()
headerExample: String = Random.generateRandomString(regex)
): PactDslRequestWithPath {
val re = Regex(regex)
if (!headerExample.matches(re)) {
Expand Down Expand Up @@ -462,7 +462,7 @@ open class PactDslRequestWithPath : PactDslRequestBase {
fun matchQuery(
parameter: String,
regex: String,
example: String = Generex(regex).random()
example: String = Random.generateRandomString(regex)
): PactDslRequestWithPath {
val re = Regex(regex)
if (!example.matches(re)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ 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.Random
import au.com.dius.pact.core.support.expressions.DataType
import au.com.dius.pact.core.support.json.JsonValue
import com.mifmif.common.regex.Generex
import org.apache.commons.lang3.time.DateFormatUtils
import org.apache.hc.core5.http.ContentType
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder
Expand Down Expand Up @@ -337,7 +337,7 @@ open class PactDslRequestWithoutPath @JvmOverloads constructor(
* @param pathRegex string path regular expression to match with
*/
@JvmOverloads
fun matchPath(pathRegex: String, path: String = Generex(pathRegex).random()): PactDslRequestWithPath {
fun matchPath(pathRegex: String, path: String = Random.generateRandomString(pathRegex)): PactDslRequestWithPath {
val re = Regex(pathRegex)
if (!path.matches(re)) {
throw InvalidMatcherException("Example \"$path\" does not match regular expression \"$pathRegex\"")
Expand All @@ -359,7 +359,7 @@ open class PactDslRequestWithoutPath @JvmOverloads constructor(
@JvmOverloads
inline fun matchPath(
pathRegex: String,
path: String = Generex(pathRegex).random(),
path: String = Random.generateRandomString(pathRegex),
addRequestMatchers: PactDslRequestWithPath.() -> PactDslRequestWithPath
): PactDslRequestWithPath = addRequestMatchers(matchPath(pathRegex, path))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import au.com.dius.pact.core.model.matchingrules.MatchingRulesImpl
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.RuleLogic
import au.com.dius.pact.core.model.matchingrules.StatusCodeMatcher
import au.com.dius.pact.core.support.Random
import au.com.dius.pact.core.support.expressions.DataType
import au.com.dius.pact.core.support.json.JsonValue
import au.com.dius.pact.core.support.jsonArray
import com.mifmif.common.regex.Generex
import org.apache.hc.core5.http.ContentType
import org.json.JSONObject
import org.w3c.dom.Document
Expand Down Expand Up @@ -298,7 +298,7 @@ open class PactDslResponse @JvmOverloads constructor(
* @param headerExample Example value to use
*/
@JvmOverloads
fun matchHeader(header: String, regexp: String?, headerExample: String = Generex(regexp).random()): PactDslResponse {
fun matchHeader(header: String, regexp: String?, headerExample: String = Random.generateRandomString(regexp.orEmpty())): PactDslResponse {
responseMatchers.addCategory("header").setRule(header, RegexMatcher(regexp!!))
responseHeaders[header] = listOf(headerExample)
return this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,19 @@ class PactDslRequestWithPathSpec extends Specification {
'$': [matchers: [[match: 'contentType', value: 'image/gif']], combine: 'AND']
]
}
@Issue('#1826')
def 'matchPath handles regular expressions with anchors'() {
given:
def request = ConsumerPactBuilder.consumer('spec')
.hasPactWith('provider')
.uponReceiving('request with a regex path')
.path('/')
when:
def result = request.matchPath('/pet/[0-9]+$')
then:
result.path ==~ /\/pet\/[0-9]+/
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,18 @@ class PactDslRequestWithoutPathSpec extends Specification {
'$': [matchers: [[match: 'contentType', value: 'image/gif']], combine: 'AND']
]
}

@Issue('#1826')
def 'matchPath handles regular expressions with anchors'() {
given:
def request = ConsumerPactBuilder.consumer('spec')
.hasPactWith('provider')
.uponReceiving('request with a regex path')

when:
def result = request.matchPath('/pet/[0-9]+$')

then:
result.path ==~ /\/pet\/[0-9]+/
}
}
1 change: 0 additions & 1 deletion core/model/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ dependencies {
implementation 'org.apache.commons:commons-collections4'
implementation 'commons-codec:commons-codec'
implementation 'org.slf4j:slf4j-api'
implementation 'com.github.mifmif:generex:1.0.2'
implementation 'javax.mail:mail:1.5.0-b01'
implementation 'io.ktor:ktor-http-jvm'
implementation 'commons-beanutils:commons-beanutils:1.9.4'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory
import au.com.dius.pact.core.support.HttpClientUtils.buildUrl
import au.com.dius.pact.core.support.Json
import au.com.dius.pact.core.support.Random
import au.com.dius.pact.core.support.Result
import au.com.dius.pact.core.support.expressions.DataType
import au.com.dius.pact.core.support.expressions.ExpressionParser
import au.com.dius.pact.core.support.expressions.MapValueResolver
import au.com.dius.pact.core.support.getOr
import au.com.dius.pact.core.support.isNotEmpty
import au.com.dius.pact.core.support.json.JsonValue
import com.mifmif.common.regex.Generex
import io.github.oshai.kotlinlogging.KLogging
import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.commons.lang3.RandomStringUtils
Expand Down Expand Up @@ -264,7 +264,7 @@ data class RegexGenerator(val regex: String) : Generator {

override fun generate(context: MutableMap<String, Any>, exampleValue: Any?): Any {
logger.debug { "Applying Generator $this" }
return Generex(regex).random()
return Random.generateRandomString(regex)
}

companion object: KLogging() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package au.com.dius.pact.core.model.generators

import spock.lang.Issue
import spock.lang.Specification

class RegexGeneratorSpec extends Specification {
def 'generates a random value when needed'() {
expect:
new RegexGenerator('\\w+').generate([:], '') ==~ /\w+/
}

@Issue('#1826')
def 'handles regex anchors'() {
expect:
new RegexGenerator('^\\w+$').generate([:], '') ==~ /\w+/
}
}
1 change: 1 addition & 0 deletions core/support/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {

implementation 'org.apache.commons:commons-text'
implementation 'commons-codec:commons-codec'
implementation 'com.github.mifmif:generex:1.0.2'

testImplementation 'org.apache.groovy:groovy'
testImplementation 'org.hamcrest:hamcrest'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package au.com.dius.pact.core.support

import com.mifmif.common.regex.Generex

/**
* Support for the generator of random values
*/
object Random {
/**
* Generate a random string from a regular expression
*/
@JvmStatic
fun generateRandomString(regex: String): String {
return if (regex.endsWith('$') && !regex.endsWith("\\$")) {
Generex(regex.trimStart('^').trimEnd('$')).random()
} else {
Generex(regex.trimStart('^')).random()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package au.com.dius.pact.core.support

import spock.lang.Issue
import spock.lang.Specification

class RandomSpec extends Specification {
def 'generates a random value from the regular expression'() {
expect:
Random.generateRandomString('\\w+') ==~ /\w+/
}

@Issue('#1826')
def 'handles regex anchors'() {
expect:
Random.generateRandomString('^\\w+$') ==~ /\w+/
}

def 'does not remove escaped values'() {
expect:
Random.generateRandomString('\\^\\w+\\$') ==~ /\^\w+\$/
}
}

0 comments on commit ec066a0

Please sign in to comment.