Skip to content

Commit ec066a0

Browse files
committedOct 23, 2024·
fix: Trim regex anchors before generating random strings from the regex #1826
1 parent 0554c1a commit ec066a0

File tree

19 files changed

+132
-26
lines changed

19 files changed

+132
-26
lines changed
 

Diff for: ‎consumer/build.gradle

-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ dependencies {
1414

1515
implementation 'org.apache.httpcomponents.client5:httpclient5-fluent'
1616
implementation 'com.googlecode.java-diff-utils:diffutils:1.3.0'
17-
implementation 'dk.brics.automaton:automaton:1.11-8'
1817
implementation('io.netty:netty-handler') {
1918
exclude module: 'netty-transport-native-kqueue'
2019
}
@@ -26,7 +25,6 @@ dependencies {
2625
exclude group: 'au.com.dius.pact.core'
2726
}
2827
implementation 'org.apache.commons:commons-lang3'
29-
implementation 'com.github.mifmif:generex:1.0.2'
3028
implementation 'org.apache.commons:commons-io:1.3.2'
3129
implementation 'org.apache.commons:commons-text:1.10.0'
3230
implementation 'org.apache.tika:tika-core'

Diff for: ‎consumer/groovy/build.gradle

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ dependencies {
1111
implementation 'org.apache.groovy:groovy'
1212
implementation 'org.apache.groovy:groovy-json'
1313
implementation 'org.apache.httpcomponents.client5:httpclient5'
14-
implementation 'com.github.mifmif:generex:1.0.2'
1514
implementation 'org.apache.commons:commons-lang3'
1615
implementation 'org.apache.commons:commons-collections4'
1716
implementation('io.pact.plugin.driver:core') {

Diff for: ‎consumer/groovy/src/main/kotlin/au/com/dius/pact/consumer/groovy/Matchers.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher
2323
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
2424
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
2525
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
26+
import au.com.dius.pact.core.support.Random
2627
import au.com.dius.pact.core.support.isNotEmpty
27-
import com.mifmif.common.regex.Generex
2828
import io.github.oshai.kotlinlogging.KLogging
2929
import org.apache.commons.lang3.time.DateFormatUtils
3030
import org.apache.commons.lang3.time.DateUtils
@@ -69,7 +69,7 @@ class RegexpMatcher @JvmOverloads constructor(
6969
value: String? = null
7070
) : Matcher(value, RegexMatcher(regex, value), if (value == null) RegexGenerator(regex) else null) {
7171
override val value: Any?
72-
get() = super.value ?: Generex(regex).random()
72+
get() = super.value ?: Random.generateRandomString(regex)
7373
}
7474

7575
class HexadecimalMatcher @JvmOverloads constructor(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package au.com.dius.pact.consumer.groovy
2+
3+
import spock.lang.Issue
4+
import spock.lang.Specification
5+
6+
class RegexpMatcherSpec extends Specification {
7+
def 'returns the value provided to the constructor'() {
8+
expect:
9+
new RegexpMatcher('\\w+', 'word').value == 'word'
10+
}
11+
12+
def 'if no value is provided to the constructor, generates a random value when needed'() {
13+
expect:
14+
new RegexpMatcher('\\w+', null).value ==~ /\w+/
15+
}
16+
17+
@Issue('#1826')
18+
def 'handles regex anchors'() {
19+
expect:
20+
new RegexpMatcher('^\\w+$', null).value ==~ /\w+/
21+
}
22+
}

Diff for: ‎consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/ArrayOfPrimitivesBuilder.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher
1010
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
1111
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
1212
import au.com.dius.pact.core.support.Json
13-
import com.mifmif.common.regex.Generex
14-
import org.json.JSONArray
13+
import au.com.dius.pact.core.support.Random
1514

1615
class ArrayOfPrimitivesBuilder {
1716

@@ -62,7 +61,7 @@ class ArrayOfPrimitivesBuilder {
6261
fun thatMatchRegex(regex: String): ArrayOfPrimitivesBuilder {
6362
this.matcher = RegexMatcher(regex)
6463
this.generator = RegexGenerator(regex)
65-
this.value = Generex(regex).random()
64+
this.value = Random.generateRandomString(regex)
6665
return this
6766
}
6867

Diff for: ‎consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/FormPostBuilder.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import au.com.dius.pact.core.model.generators.RegexGenerator
1313
import au.com.dius.pact.core.model.generators.TimeGenerator
1414
import au.com.dius.pact.core.model.generators.UuidGenerator
1515
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory
16+
import au.com.dius.pact.core.support.Random
1617
import au.com.dius.pact.core.support.expressions.DataType
17-
import com.mifmif.common.regex.Generex
1818
import org.apache.commons.lang3.time.DateFormatUtils
1919
import org.apache.commons.lang3.time.FastDateFormat
2020
import java.net.URLEncoder
@@ -100,7 +100,7 @@ class FormPostBuilder(
100100
*/
101101
fun stringMatcher(name: String, regex: String): FormPostBuilder {
102102
generators.addGenerator(Category.BODY, name, RegexGenerator(regex))
103-
return stringMatcher(name, regex, Generex(regex).random())
103+
return stringMatcher(name, regex, Random.generateRandomString(regex))
104104
}
105105

106106
/**

Diff for: ‎consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslJsonBody.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ import au.com.dius.pact.core.model.matchingrules.TypeMatcher
3131
import au.com.dius.pact.core.model.matchingrules.ValuesMatcher
3232
import au.com.dius.pact.core.model.matchingrules.expressions.MatchingRuleDefinition
3333
import au.com.dius.pact.core.support.Json.toJson
34+
import au.com.dius.pact.core.support.Random
3435
import au.com.dius.pact.core.support.expressions.DataType.Companion.from
3536
import au.com.dius.pact.core.support.json.JsonValue
3637
import au.com.dius.pact.core.support.padTo
37-
import com.mifmif.common.regex.Generex
3838
import org.apache.commons.lang3.StringUtils
3939
import org.apache.commons.lang3.time.DateFormatUtils
4040
import org.apache.commons.lang3.time.FastDateFormat
@@ -795,7 +795,7 @@ open class PactDslJsonBody : DslPart {
795795
*/
796796
fun stringMatcher(name: String, regex: String): PactDslJsonBody {
797797
generators.addGenerator(Category.BODY, matcherKey(name, rootPath), RegexGenerator(regex))
798-
stringMatcher(name, regex, *examples(Generex(regex).random()))
798+
stringMatcher(name, regex, *examples(Random.generateRandomString(regex)))
799799
return this
800800
}
801801

Diff for: ‎consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslJsonRootValue.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import au.com.dius.pact.core.model.matchingrules.RegexMatcher
2121
import au.com.dius.pact.core.model.matchingrules.RuleLogic
2222
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
2323
import au.com.dius.pact.core.support.Json.toJson
24+
import au.com.dius.pact.core.support.Random
2425
import au.com.dius.pact.core.support.expressions.DataType.Companion.from
2526
import au.com.dius.pact.core.support.json.JsonValue
26-
import com.mifmif.common.regex.Generex
2727
import org.apache.commons.lang3.StringUtils
2828
import org.apache.commons.lang3.time.DateFormatUtils
2929
import org.apache.commons.lang3.time.FastDateFormat
@@ -612,7 +612,7 @@ open class PactDslJsonRootValue : DslPart("", "") {
612612
fun stringMatcher(regex: String): PactDslJsonRootValue {
613613
val rootValue = PactDslJsonRootValue()
614614
rootValue.generators.addGenerator(Category.BODY, "", RegexGenerator(regex))
615-
rootValue.value = Generex(regex).random()
615+
rootValue.value = Random.generateRandomString(regex)
616616
rootValue.setMatcher(rootValue.regexp(regex))
617617
return rootValue
618618
}

Diff for: ‎consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithPath.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import au.com.dius.pact.core.model.matchingrules.ContentTypeMatcher
1717
import au.com.dius.pact.core.model.matchingrules.MatchingRules
1818
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
1919
import au.com.dius.pact.core.model.queryStringToMap
20+
import au.com.dius.pact.core.support.Random
2021
import au.com.dius.pact.core.support.expressions.DataType
2122
import au.com.dius.pact.core.support.json.JsonValue
22-
import com.mifmif.common.regex.Generex
2323
import org.apache.commons.lang3.time.DateFormatUtils
2424
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder
2525
import org.apache.hc.core5.http.ContentType
@@ -397,7 +397,7 @@ open class PactDslRequestWithPath : PactDslRequestBase {
397397
* @param pathRegex regular expression to use to match paths
398398
*/
399399
@JvmOverloads
400-
fun matchPath(pathRegex: String, path: String = Generex(pathRegex).random()): PactDslRequestWithPath {
400+
fun matchPath(pathRegex: String, path: String = Random.generateRandomString(pathRegex)): PactDslRequestWithPath {
401401
val re = Regex(pathRegex)
402402
if (!path.matches(re)) {
403403
throw InvalidMatcherException("Example \"$path\" does not match regular expression \"$pathRegex\"")
@@ -420,7 +420,7 @@ open class PactDslRequestWithPath : PactDslRequestBase {
420420
fun matchHeader(
421421
header: String,
422422
regex: String,
423-
headerExample: String = Generex(regex).random()
423+
headerExample: String = Random.generateRandomString(regex)
424424
): PactDslRequestWithPath {
425425
val re = Regex(regex)
426426
if (!headerExample.matches(re)) {
@@ -462,7 +462,7 @@ open class PactDslRequestWithPath : PactDslRequestBase {
462462
fun matchQuery(
463463
parameter: String,
464464
regex: String,
465-
example: String = Generex(regex).random()
465+
example: String = Random.generateRandomString(regex)
466466
): PactDslRequestWithPath {
467467
val re = Regex(regex)
468468
if (!example.matches(re)) {

Diff for: ‎consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPath.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import au.com.dius.pact.core.model.generators.ProviderStateGenerator
1010
import au.com.dius.pact.core.model.matchingrules.ContentTypeMatcher
1111
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
1212
import au.com.dius.pact.core.model.queryStringToMap
13+
import au.com.dius.pact.core.support.Random
1314
import au.com.dius.pact.core.support.expressions.DataType
1415
import au.com.dius.pact.core.support.json.JsonValue
15-
import com.mifmif.common.regex.Generex
1616
import org.apache.commons.lang3.time.DateFormatUtils
1717
import org.apache.hc.core5.http.ContentType
1818
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder
@@ -337,7 +337,7 @@ open class PactDslRequestWithoutPath @JvmOverloads constructor(
337337
* @param pathRegex string path regular expression to match with
338338
*/
339339
@JvmOverloads
340-
fun matchPath(pathRegex: String, path: String = Generex(pathRegex).random()): PactDslRequestWithPath {
340+
fun matchPath(pathRegex: String, path: String = Random.generateRandomString(pathRegex)): PactDslRequestWithPath {
341341
val re = Regex(pathRegex)
342342
if (!path.matches(re)) {
343343
throw InvalidMatcherException("Example \"$path\" does not match regular expression \"$pathRegex\"")
@@ -359,7 +359,7 @@ open class PactDslRequestWithoutPath @JvmOverloads constructor(
359359
@JvmOverloads
360360
inline fun matchPath(
361361
pathRegex: String,
362-
path: String = Generex(pathRegex).random(),
362+
path: String = Random.generateRandomString(pathRegex),
363363
addRequestMatchers: PactDslRequestWithPath.() -> PactDslRequestWithPath
364364
): PactDslRequestWithPath = addRequestMatchers(matchPath(pathRegex, path))
365365

Diff for: ‎consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslResponse.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ import au.com.dius.pact.core.model.matchingrules.MatchingRulesImpl
2929
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
3030
import au.com.dius.pact.core.model.matchingrules.RuleLogic
3131
import au.com.dius.pact.core.model.matchingrules.StatusCodeMatcher
32+
import au.com.dius.pact.core.support.Random
3233
import au.com.dius.pact.core.support.expressions.DataType
3334
import au.com.dius.pact.core.support.json.JsonValue
3435
import au.com.dius.pact.core.support.jsonArray
35-
import com.mifmif.common.regex.Generex
3636
import org.apache.hc.core5.http.ContentType
3737
import org.json.JSONObject
3838
import org.w3c.dom.Document
@@ -298,7 +298,7 @@ open class PactDslResponse @JvmOverloads constructor(
298298
* @param headerExample Example value to use
299299
*/
300300
@JvmOverloads
301-
fun matchHeader(header: String, regexp: String?, headerExample: String = Generex(regexp).random()): PactDslResponse {
301+
fun matchHeader(header: String, regexp: String?, headerExample: String = Random.generateRandomString(regexp.orEmpty())): PactDslResponse {
302302
responseMatchers.addCategory("header").setRule(header, RegexMatcher(regexp!!))
303303
responseHeaders[header] = listOf(headerExample)
304304
return this

Diff for: ‎consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithPathSpec.groovy

+15
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,19 @@ class PactDslRequestWithPathSpec extends Specification {
289289
'$': [matchers: [[match: 'contentType', value: 'image/gif']], combine: 'AND']
290290
]
291291
}
292+
293+
@Issue('#1826')
294+
def 'matchPath handles regular expressions with anchors'() {
295+
given:
296+
def request = ConsumerPactBuilder.consumer('spec')
297+
.hasPactWith('provider')
298+
.uponReceiving('request with a regex path')
299+
.path('/')
300+
301+
when:
302+
def result = request.matchPath('/pet/[0-9]+$')
303+
304+
then:
305+
result.path ==~ /\/pet\/[0-9]+/
306+
}
292307
}

Diff for: ‎consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslRequestWithoutPathSpec.groovy

+14
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,18 @@ class PactDslRequestWithoutPathSpec extends Specification {
116116
'$': [matchers: [[match: 'contentType', value: 'image/gif']], combine: 'AND']
117117
]
118118
}
119+
120+
@Issue('#1826')
121+
def 'matchPath handles regular expressions with anchors'() {
122+
given:
123+
def request = ConsumerPactBuilder.consumer('spec')
124+
.hasPactWith('provider')
125+
.uponReceiving('request with a regex path')
126+
127+
when:
128+
def result = request.matchPath('/pet/[0-9]+$')
129+
130+
then:
131+
result.path ==~ /\/pet\/[0-9]+/
132+
}
119133
}

Diff for: ‎core/model/build.gradle

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ dependencies {
1414
implementation 'org.apache.commons:commons-collections4'
1515
implementation 'commons-codec:commons-codec'
1616
implementation 'org.slf4j:slf4j-api'
17-
implementation 'com.github.mifmif:generex:1.0.2'
1817
implementation 'javax.mail:mail:1.5.0-b01'
1918
implementation 'io.ktor:ktor-http-jvm'
2019
implementation 'commons-beanutils:commons-beanutils:1.9.4'

Diff for: ‎core/model/src/main/kotlin/au/com/dius/pact/core/model/generators/Generator.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import au.com.dius.pact.core.model.PactSpecVersion
44
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory
55
import au.com.dius.pact.core.support.HttpClientUtils.buildUrl
66
import au.com.dius.pact.core.support.Json
7+
import au.com.dius.pact.core.support.Random
78
import au.com.dius.pact.core.support.Result
89
import au.com.dius.pact.core.support.expressions.DataType
910
import au.com.dius.pact.core.support.expressions.ExpressionParser
1011
import au.com.dius.pact.core.support.expressions.MapValueResolver
1112
import au.com.dius.pact.core.support.getOr
1213
import au.com.dius.pact.core.support.isNotEmpty
1314
import au.com.dius.pact.core.support.json.JsonValue
14-
import com.mifmif.common.regex.Generex
1515
import io.github.oshai.kotlinlogging.KLogging
1616
import io.github.oshai.kotlinlogging.KotlinLogging
1717
import org.apache.commons.lang3.RandomStringUtils
@@ -264,7 +264,7 @@ data class RegexGenerator(val regex: String) : Generator {
264264

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

270270
companion object: KLogging() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package au.com.dius.pact.core.model.generators
2+
3+
import spock.lang.Issue
4+
import spock.lang.Specification
5+
6+
class RegexGeneratorSpec extends Specification {
7+
def 'generates a random value when needed'() {
8+
expect:
9+
new RegexGenerator('\\w+').generate([:], '') ==~ /\w+/
10+
}
11+
12+
@Issue('#1826')
13+
def 'handles regex anchors'() {
14+
expect:
15+
new RegexGenerator('^\\w+$').generate([:], '') ==~ /\w+/
16+
}
17+
}

Diff for: ‎core/support/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies {
1212

1313
implementation 'org.apache.commons:commons-text'
1414
implementation 'commons-codec:commons-codec'
15+
implementation 'com.github.mifmif:generex:1.0.2'
1516

1617
testImplementation 'org.apache.groovy:groovy'
1718
testImplementation 'org.hamcrest:hamcrest'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package au.com.dius.pact.core.support
2+
3+
import com.mifmif.common.regex.Generex
4+
5+
/**
6+
* Support for the generator of random values
7+
*/
8+
object Random {
9+
/**
10+
* Generate a random string from a regular expression
11+
*/
12+
@JvmStatic
13+
fun generateRandomString(regex: String): String {
14+
return if (regex.endsWith('$') && !regex.endsWith("\\$")) {
15+
Generex(regex.trimStart('^').trimEnd('$')).random()
16+
} else {
17+
Generex(regex.trimStart('^')).random()
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package au.com.dius.pact.core.support
2+
3+
import spock.lang.Issue
4+
import spock.lang.Specification
5+
6+
class RandomSpec extends Specification {
7+
def 'generates a random value from the regular expression'() {
8+
expect:
9+
Random.generateRandomString('\\w+') ==~ /\w+/
10+
}
11+
12+
@Issue('#1826')
13+
def 'handles regex anchors'() {
14+
expect:
15+
Random.generateRandomString('^\\w+$') ==~ /\w+/
16+
}
17+
18+
def 'does not remove escaped values'() {
19+
expect:
20+
Random.generateRandomString('\\^\\w+\\$') ==~ /\^\w+\$/
21+
}
22+
}

0 commit comments

Comments
 (0)
Please sign in to comment.