Fixed invalid escaping of characters for regex
without this change some regex contained invalid characters. Even though the code compiles the regular expressions passed to json assert are invalid. with this change that gets fixed and tests are added. We're now checking if the code can be executed and not only compiled. We also check if WireMock fed with the stubs generated from these cases works fine and not only do we check if stub mappings are properly generated. fixes #250
This commit is contained in:
@@ -49,7 +49,7 @@ abstract class PatternValueDslProperty<T extends DslProperty> {
|
||||
}
|
||||
|
||||
T anyIpAddress() {
|
||||
return createAndValidateProperty(RegexPatterns.IP_ADDRESS)
|
||||
return createAndValidateProperty(RegexPatterns.IP_ADDRESS, "192.168.0." + this.random.nextInt(10))
|
||||
}
|
||||
|
||||
T anyHostname() {
|
||||
|
||||
@@ -37,9 +37,9 @@ class RegexPatterns {
|
||||
protected static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
|
||||
protected static final Pattern NUMBER = Pattern.compile('-?\\d*(\\.\\d+)?')
|
||||
protected static final Pattern IP_ADDRESS = Pattern.compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])')
|
||||
protected static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?')
|
||||
protected static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}');
|
||||
protected static final Pattern URL = Pattern.compile('((www\\.|(http|https|ftp|news|file)+\\:\\/\\/)[_.a-z0-9-]+\\.[a-z0-9\\/_:@=.+?,##%&~-]*[^.|\\\'|\\# |!|\\(|?|,| |>|<|;|\\)])')
|
||||
protected static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):/)/?([^:/\\s]+)(:[0-9]{1,5})?')
|
||||
protected static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}')
|
||||
protected static final Pattern URL = UrlHelper.URL
|
||||
protected static final Pattern UUID = Pattern.compile('[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}')
|
||||
protected static final Pattern ANY_DATE = Pattern.compile('(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])')
|
||||
protected static final Pattern ANY_DATE_TIME = Pattern.compile('([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])')
|
||||
@@ -118,3 +118,45 @@ class RegexPatterns {
|
||||
return ".*--(.*)\r\nContent-Disposition: form-data; name=\"$name\"; filename=\"$filename\"\r\n(Content-Type: .*\r\n)?(Content-Length: \\d+\r\n)?\r\n$content\r\n--\\1.*";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from https://gist.github.com/skeller88/5eb73dc0090d4ff1249a
|
||||
*/
|
||||
class UrlHelper {
|
||||
/**
|
||||
* Example: "http". Also called 'protocol'.
|
||||
* Scheme component is optional, even though the RFC doesn't make it optional. Since this regex is validating a
|
||||
* submitted callback url, which determines where the browser will navigate to after a successful authentication,
|
||||
* the browser will use http or https for the scheme by default.
|
||||
* Not borrowed from dperini in order to allow any scheme type.
|
||||
*/
|
||||
private static final String REGEX_SCHEME = "[A-Za-z][+-.\\w^_]*:"
|
||||
|
||||
// Example: "//".
|
||||
private static final String REGEX_AUTHORATIVE_DECLARATION = "/{2}"
|
||||
|
||||
// Optional component. Example: "suzie:abc123@". The use of the format "user:password" is deprecated.
|
||||
private static final String REGEX_USERINFO = "(?:\\S+(?::\\S*)?@)?"
|
||||
|
||||
// Examples: "fitbit.com", "22.231.113.64".
|
||||
private static final String REGEX_HOST = "(?:" +
|
||||
// @Author = http://www.regular-expressions.info/examples.html
|
||||
// IP address
|
||||
"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +
|
||||
"|" +
|
||||
// host name
|
||||
"(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" +
|
||||
// domain name
|
||||
"(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" +
|
||||
// TLD identifier must have >= 2 characters
|
||||
"(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))"
|
||||
|
||||
// Example: ":8042".
|
||||
private static final String REGEX_PORT = "(?::\\d{2,5})?"
|
||||
|
||||
//Example: "/user/heartrate?foo=bar#element1".
|
||||
private static final String REGEX_RESOURCE_PATH = "(?:/\\S*)?"
|
||||
|
||||
protected static final Pattern URL = Pattern.compile("^(?:(?:" + REGEX_SCHEME + REGEX_AUTHORATIVE_DECLARATION + ")?" +
|
||||
REGEX_USERINFO + REGEX_HOST + REGEX_PORT + REGEX_RESOURCE_PATH + ")\$")
|
||||
}
|
||||
|
||||
@@ -55,13 +55,76 @@ class RegexPatternsSpec extends Specification {
|
||||
'a.b.' || false
|
||||
}
|
||||
|
||||
// @see http://formvalidation.io/validators/uri/
|
||||
def "should generate a regex for url [#textToMatch] that is a match [#shouldMatch]"() {
|
||||
expect:
|
||||
shouldMatch == Pattern.compile(regexPatterns.url()).matcher(textToMatch).matches()
|
||||
where:
|
||||
textToMatch || shouldMatch
|
||||
'ftp://asd.com:9090/asd/a?a=b' || true
|
||||
'a.b.' || false
|
||||
textToMatch || shouldMatch
|
||||
'ftp://asd.com:9090/asd/a?a=b' || true
|
||||
'http://foo.com/blah_blah' || true
|
||||
'http://foo.com/blah_blah/' || true
|
||||
'http://foo.com/blah_blah_(wikipedia)' || true
|
||||
'http://foo.com/blah_blah_(wikipedia)_(again)' || true
|
||||
'http://www.example.com/wpstyle/?p=364' || true
|
||||
'https://www.example.com/foo/?bar=baz&inga=42&quux' || true
|
||||
'http://✪df.ws/123' || true
|
||||
'http://userid:password@example.com:8080' || true
|
||||
'http://userid:password@example.com:8080/' || true
|
||||
'http://userid@example.com' || true
|
||||
'http://userid@example.com/' || true
|
||||
'http://userid@example.com:8080' || true
|
||||
'http://userid@example.com:8080/' || true
|
||||
'http://userid:password@example.com' || true
|
||||
'http://userid:password@example.com/' || true
|
||||
'http://142.42.1.1/' || true
|
||||
'http://142.42.1.1:8080/' || true
|
||||
'http://⌘.ws' || true
|
||||
'http://⌘.ws/' || true
|
||||
'http://foo.com/blah_(wikipedia)#cite-1' || true
|
||||
'http://foo.com/blah_(wikipedia)_blah#cite-1' || true
|
||||
'http://foo.com/unicode_(✪)_in_parens' || true
|
||||
'http://foo.com/(something)?after=parens' || true
|
||||
'http://☺.damowmow.com/' || true
|
||||
'http://code.google.com/events/#&product=browser' || true
|
||||
'http://j.mp' || true
|
||||
'ftp://foo.bar/baz' || true
|
||||
'http://foo.bar/?q=Test%20URL-encoded%20stuff' || true
|
||||
'http://1337.net' || true
|
||||
'http://a.b-c.de' || true
|
||||
'http://223.255.255.254' || true
|
||||
'foo.com' || true
|
||||
'a.b.' || false
|
||||
'http://' || false
|
||||
'http://.' || false
|
||||
'http://..' || false
|
||||
'http://../' || false
|
||||
'http://?' || false
|
||||
'http://??' || false
|
||||
'http://??/' || false
|
||||
'http://#' || false
|
||||
'http://##' || false
|
||||
'http://##/' || false
|
||||
'http://foo.bar?q=Spaces should be encoded' || false
|
||||
'//' || false
|
||||
'//a' || false
|
||||
'///a' || false
|
||||
'///' || false
|
||||
'http:///a' || false
|
||||
'rdar://1234' || false
|
||||
'h://test' || false
|
||||
'http:// shouldfail.com' || false
|
||||
':// should fail' || false
|
||||
'http://foo.bar/foo(bar)baz quux' || false
|
||||
'http://-error-.invalid/' || false
|
||||
'http://-a.b.co' || false
|
||||
'http://a.b-.co' || false
|
||||
'http://1.1.1.1.1' || false
|
||||
'http://123.123.123' || false
|
||||
'http://3628126748' || false
|
||||
'http://.www.foo.bar/' || false
|
||||
'http://www.foo.bar./' || false
|
||||
'http://.www.foo.bar./' || false
|
||||
}
|
||||
|
||||
def "should generate a regex for a number [#textToMatch] that is a match [#shouldMatch]"() {
|
||||
|
||||
@@ -49,6 +49,27 @@
|
||||
<artifactId>spock-global-unroll</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-wiremock</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jetty</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@@ -16,13 +16,18 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.wiremock
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule
|
||||
import com.github.tomakehurst.wiremock.matching.RegexPattern
|
||||
import com.github.tomakehurst.wiremock.stubbing.StubMapping
|
||||
import groovy.json.JsonOutput
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.skyscreamer.jsonassert.JSONAssert
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate
|
||||
import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockStubMapping
|
||||
import org.springframework.cloud.contract.verifier.file.ContractMetadata
|
||||
import org.springframework.http.RequestEntity
|
||||
import org.springframework.util.SocketUtils
|
||||
import spock.lang.Issue
|
||||
import spock.lang.Specification
|
||||
|
||||
@@ -30,8 +35,15 @@ import java.util.regex.Pattern
|
||||
|
||||
class DslToWireMockClientConverterSpec extends Specification {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tmpFolder = new TemporaryFolder()
|
||||
static int port = SocketUtils.findAvailableTcpPort()
|
||||
@Rule public WireMockRule wireMockRule = new WireMockRule(port)
|
||||
@Rule public TemporaryFolder tmpFolder = new TemporaryFolder()
|
||||
TestRestTemplate restTemplate = new TestRestTemplate()
|
||||
String url
|
||||
|
||||
def setup() {
|
||||
url = "http://localhost:${port}"
|
||||
}
|
||||
|
||||
def "should convert DSL file to WireMock JSON"() {
|
||||
given:
|
||||
@@ -56,7 +68,11 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
{"request":{"method":"PUT","urlPattern":"/[0-9]{2}"},"response":{"status":200}}
|
||||
''', json, false)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
StubMapping mapping = stubMappingIsValidWireMockStub(json)
|
||||
and:
|
||||
wireMockRule.addStubMapping(mapping)
|
||||
and:
|
||||
restTemplate.exchange(RequestEntity.put("${url}/12".toURI()).body(""), String)
|
||||
}
|
||||
|
||||
@Issue("196")
|
||||
@@ -89,7 +105,11 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
}}
|
||||
''', json, false)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
StubMapping mapping = stubMappingIsValidWireMockStub(json)
|
||||
and:
|
||||
wireMockRule.addStubMapping(mapping)
|
||||
and:
|
||||
restTemplate.exchange(RequestEntity.get("${url}/foo".toURI()).build(), String)
|
||||
}
|
||||
|
||||
def "should convert DSL file with a nested list to WireMock JSON"() {
|
||||
@@ -104,7 +124,6 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
url '/api/12'
|
||||
headers {
|
||||
header 'Content-Type': 'application/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+json'
|
||||
|
||||
}
|
||||
body '''
|
||||
[{
|
||||
@@ -195,10 +214,43 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
}
|
||||
''', json, false)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
StubMapping mapping = stubMappingIsValidWireMockStub(json)
|
||||
and:
|
||||
wireMockRule.addStubMapping(mapping)
|
||||
and:
|
||||
restTemplate.exchange(RequestEntity.put("${url}/api/12".toURI())
|
||||
.header('Content-Type', 'application/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+json')
|
||||
.body('''
|
||||
[{
|
||||
"created_at": "Sat Jul 26 09:38:57 +0000 2014",
|
||||
"id": 492967299297845248,
|
||||
"id_str": "492967299297845248",
|
||||
"text": "Gonna see you at Warsaw",
|
||||
"place":
|
||||
{
|
||||
"attributes":{},
|
||||
"bounding_box":
|
||||
{
|
||||
"coordinates":
|
||||
[[
|
||||
[-77.119759,38.791645],
|
||||
[-76.909393,38.791645],
|
||||
[-76.909393,38.995548],
|
||||
[-77.119759,38.995548]
|
||||
]],
|
||||
"type":"Polygon"
|
||||
},
|
||||
"country":"United States",
|
||||
"country_code":"US",
|
||||
"full_name":"Washington, DC",
|
||||
"id":"01fbe706f872cb32",
|
||||
"name":"Washington",
|
||||
"place_type":"city",
|
||||
"url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json"
|
||||
}
|
||||
}]'''), String)
|
||||
}
|
||||
|
||||
|
||||
@Issue("262")
|
||||
def "should create stub with map inside list"() {
|
||||
given:
|
||||
@@ -233,7 +285,13 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
{"request":{"urlPath":"/foos","method":"GET"},"response":{"body":"[{\\"id\\":\\"123\\"},{\\"id\\":\\"567\\"}]"}}
|
||||
''', json, false)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
StubMapping mapping = stubMappingIsValidWireMockStub(json)
|
||||
and:
|
||||
wireMockRule.addStubMapping(mapping)
|
||||
and:
|
||||
def response = restTemplate.exchange(RequestEntity.get("${url}/foos".toURI()).build(), String)
|
||||
response.headers.get('Content-Type') == ['application/json']
|
||||
JSONAssert.assertEquals('''[ { "id":"123" }, { "id": "567" } ]''', response.body, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +323,12 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
and:
|
||||
!json.contains('cursor')
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
StubMapping mapping = stubMappingIsValidWireMockStub(json)
|
||||
and:
|
||||
wireMockRule.addStubMapping(mapping)
|
||||
and:
|
||||
def response = restTemplate.exchange(RequestEntity.get("${url}/foos".toURI()).build(), String)
|
||||
response.body
|
||||
}
|
||||
|
||||
def 'should convert dsl to wiremock to show it in the docs'() {
|
||||
@@ -332,7 +395,17 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
// end::wiremock[]
|
||||
, json, false)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
StubMapping mapping = stubMappingIsValidWireMockStub(json)
|
||||
and:
|
||||
wireMockRule.addStubMapping(mapping)
|
||||
and:
|
||||
def response = restTemplate.exchange(RequestEntity.post("${url}/users/password".toURI())
|
||||
.header("Content-Type", "application/json")
|
||||
.body('''{"email":"abc@abc.com", "callback_url":"http://partners.com"}''')
|
||||
, String)
|
||||
response.headers.get('Content-Type') == ['application/json']
|
||||
response.statusCodeValue == 404
|
||||
JSONAssert.assertEquals('''{"code":"123123","message":"User not found by email == [not.existing@user.com]"}"''', response.body, false)
|
||||
}
|
||||
|
||||
def 'should convert dsl to wiremock with stub matchers'() {
|
||||
@@ -343,7 +416,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
file.write('''
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request {
|
||||
method 'GET'
|
||||
method 'POST'
|
||||
urlPath '/get'
|
||||
body([
|
||||
duck: 123,
|
||||
@@ -451,7 +524,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
{
|
||||
"request" : {
|
||||
"urlPath" : "/get",
|
||||
"method" : "GET",
|
||||
"method" : "POST",
|
||||
"headers" : {
|
||||
"Content-Type" : {
|
||||
"matches" : "application/json.*"
|
||||
@@ -501,7 +574,60 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
//end::matchers[]
|
||||
, json, false)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
StubMapping mapping = stubMappingIsValidWireMockStub(json)
|
||||
and:
|
||||
wireMockRule.addStubMapping(mapping)
|
||||
and:
|
||||
def response = restTemplate.exchange(RequestEntity.post("${url}/get".toURI())
|
||||
.header("Content-Type", "application/json")
|
||||
.body(JsonOutput.toJson([
|
||||
duck: 123,
|
||||
alpha: "abc",
|
||||
number: 123,
|
||||
aBoolean: true,
|
||||
date: "2017-01-01",
|
||||
dateTime: "2017-01-01T01:23:45",
|
||||
time: "01:02:34",
|
||||
valueWithoutAMatcher: "foo",
|
||||
valueWithTypeMatch: "string",
|
||||
list: [
|
||||
some: [
|
||||
nested: [
|
||||
json: "with value",
|
||||
anothervalue: 4
|
||||
]
|
||||
],
|
||||
someother: [
|
||||
nested: [
|
||||
json: "with value",
|
||||
anothervalue: 4
|
||||
]
|
||||
]
|
||||
]
|
||||
]))
|
||||
, String)
|
||||
response.headers.get('Content-Type') == ['application/json']
|
||||
response.statusCodeValue == 200
|
||||
JSONAssert.assertEquals(JsonOutput.toJson([
|
||||
duck: 123,
|
||||
alpha: "abc",
|
||||
number: 123,
|
||||
aBoolean: true,
|
||||
date: "2017-01-01",
|
||||
dateTime: "2017-01-01T01:23:45",
|
||||
time: "01:02:34",
|
||||
valueWithoutAMatcher: "foo",
|
||||
valueWithTypeMatch: "string",
|
||||
valueWithMin: [
|
||||
1,2,3
|
||||
],
|
||||
valueWithMax: [
|
||||
1,2,3
|
||||
],
|
||||
valueWithMinMax: [
|
||||
1,2,3
|
||||
],
|
||||
]), response.body, false)
|
||||
}
|
||||
|
||||
def 'should convert dsl to wiremock with stub matchers with docs example'() {
|
||||
@@ -514,7 +640,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
priority 1
|
||||
request {
|
||||
method 'POST'
|
||||
url '/users/password'
|
||||
url '/users/password2'
|
||||
headers {
|
||||
header 'Content-Type': 'application/json'
|
||||
}
|
||||
@@ -550,7 +676,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
'''
|
||||
{
|
||||
"request" : {
|
||||
"url" : "/users/password",
|
||||
"url" : "/users/password2",
|
||||
"method" : "POST",
|
||||
"bodyPatterns" : [ {
|
||||
"matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})/)]"
|
||||
@@ -575,15 +701,26 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
'''
|
||||
, json, false)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
StubMapping mapping = stubMappingIsValidWireMockStub(json)
|
||||
and:
|
||||
wireMockRule.addStubMapping(mapping)
|
||||
and:
|
||||
def response = restTemplate.exchange(RequestEntity.post("${url}/users/password2".toURI())
|
||||
.header("Content-Type", "application/json")
|
||||
.body('''{"email":"abc@abc.com", "callback_url":"http://partners.com"}''')
|
||||
, String)
|
||||
response.headers.get('Content-Type') == ['application/json']
|
||||
response.statusCodeValue == 404
|
||||
JSONAssert.assertEquals('''{"code":"123123","message":"User not found by email == [not.existing@user.com]"}"''', response.body, false)
|
||||
}
|
||||
|
||||
void stubMappingIsValidWireMockStub(String mappingDefinition) {
|
||||
|
||||
StubMapping stubMappingIsValidWireMockStub(String mappingDefinition) {
|
||||
StubMapping stubMapping = WireMockStubMapping.buildFrom(mappingDefinition)
|
||||
stubMapping.request.bodyPatterns.findAll { it.isPresent() && it instanceof RegexPattern }.every {
|
||||
Pattern.compile(it.getValue())
|
||||
}
|
||||
assert !mappingDefinition.contains('org.springframework.cloud.contract.spec.internal')
|
||||
return stubMapping
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.util;
|
||||
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeJava;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.toomuchcoding.jsonassert.JsonVerifiable;
|
||||
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeJava;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link MethodBufferingJsonVerifiable} that contains a list
|
||||
* of String method commands that need to be executed to assert JSONs.
|
||||
@@ -212,7 +212,8 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
|
||||
* an double escaped text. Related to https://github.com/spring-cloud/spring-cloud-contract/issues/169
|
||||
*/
|
||||
private String escapedHackedJavaText(String value) {
|
||||
return escapeJava(value).replace("\\\"", "\"");
|
||||
return escapeJava(value)
|
||||
.replace("\\\"", "\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -133,7 +133,8 @@ class JsonToJsonPathsConverter {
|
||||
throw new IllegalStateException("Value [${bodyMatcher.path()}] not found in JSON [${JsonOutput.toJson(body)}]", e)
|
||||
}
|
||||
} else {
|
||||
return "=~ /(${value})/"
|
||||
String convertedValue = value.toString().replace('/', '\\\\/')
|
||||
return "=~ /(${convertedValue})/"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class MapConverter {
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return extractValue(value, closure);
|
||||
return extractValue(value, closure)
|
||||
} else if (value instanceof Map) {
|
||||
return convert(value as Map, closure)
|
||||
} else if (value instanceof List) {
|
||||
@@ -77,7 +77,7 @@ class MapConverter {
|
||||
if (newValue instanceof Map || newValue instanceof List || newValue instanceof String && value) {
|
||||
return transformValues(newValue, closure)
|
||||
}
|
||||
return newValue;
|
||||
return newValue
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1039,8 +1039,8 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub
|
||||
then:
|
||||
test.contains('assertThatJson(parsedJson).field("aBoolean").matches("(true|false)")')
|
||||
test.contains('assertThatJson(parsedJson).field("alpha").matches("[\\\\p{L}]*")')
|
||||
test.contains('assertThatJson(parsedJson).field("hostname").matches("((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?")')
|
||||
test.contains('assertThatJson(parsedJson).field("url").matches("((www\\\\.|(http|https|ftp|news|file)+\\\\:\\\\/\\\\/)[_.a-z0-9-]+\\\\.[a-z0-9\\\\/_:@=.+?,##%&~-]*[^.|\\\\\'|\\\\# |!|\\\\(|?|,| |>|<|;|\\\\)])")')
|
||||
test.contains('assertThatJson(parsedJson).field("hostname").matches("((http[s]?|ftp):/)/?([^:/\\\\s]+)(:[0-9]{1,5})?")')
|
||||
test.contains('assertThatJson(parsedJson).field("url").matches("^(?:(?:[A-Za-z][+-.\\\\w^_]*:/{2})?(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("number").matches("-?\\\\d*(\\\\.\\\\d+)?")')
|
||||
test.contains('assertThatJson(parsedJson).field("email").matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4}")')
|
||||
test.contains('assertThatJson(parsedJson).field("ip").matches("([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])")')
|
||||
@@ -1053,8 +1053,19 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub
|
||||
test.contains('assertThatJson(parsedJson).field("nonEmptyString").matches(".+")')
|
||||
test.contains('assertThatJson(parsedJson).field("anyOf").matches("^foo' + endOfLineRegexSymbol + '|^bar' + endOfLineRegexSymbol + '")')
|
||||
!test.contains('cursor')
|
||||
!test.contains('REGEXP>>')
|
||||
and:
|
||||
SyntaxChecker.tryToCompile(methodBuilderName, blockBuilder.toString())
|
||||
and:
|
||||
String jsonSample = '''\
|
||||
String json = "{\\"duck\\":\\"8\\",\\"alpha\\":\\"YAJEOWYGMFBEWPMEMAZI\\",\\"number\\":-2095030871,\\"aBoolean\\":true,\\"ip\\":\\"129.168.99.100\\",\\"hostname\\":\\"http://foo389886219.com\\",\\"email\\":\\"foo@bar1367573183.com\\",\\"url\\":\\"http://foo-597104692.com\\",\\"uuid\\":\\"e436b817-b764-49a2-908e-967f2f99eb9f\\",\\"date\\":\\"2014-04-14\\",\\"dateTime\\":\\"2011-01-11T12:23:34\\",\\"time\\":\\"12:20:30\\",\\"iso8601WithOffset\\":\\"2015-05-15T12:23:34.123Z\\",\\"nonBlankString\\":\\"EPZWVIRHSUAPBJMMQSFO\\",\\"nonEmptyString\\":\\"RVMFDSEQFHRQFVUVQPIA\\",\\"anyOf\\":\\"foo\\"}";
|
||||
DocumentContext parsedJson = JsonPath.parse(json);
|
||||
'''
|
||||
and:
|
||||
LinkedList<String> lines = [] as LinkedList<String>
|
||||
test.eachLine { if (it.contains("assertThatJson")) lines << it else it }
|
||||
lines.addFirst(jsonSample)
|
||||
SyntaxChecker.tryToRun(methodBuilderName, lines.join("\n"))
|
||||
where:
|
||||
methodBuilderName | methodBuilder | endOfLineRegexSymbol
|
||||
"JaxRsClientSpockMethodRequestProcessingBodyBuilder"| { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$'
|
||||
|
||||
@@ -1880,7 +1880,7 @@ World.'''"""
|
||||
}
|
||||
|
||||
@Issue('#149')
|
||||
def "should allow easier way of providing dynamic values for [#methodBuilderName]"() {
|
||||
def "should allow easier way of providing dynamic values for [#methodBuilderName]"() {
|
||||
given:
|
||||
Contract contractDsl = Contract.make {
|
||||
request {
|
||||
@@ -1940,8 +1940,8 @@ World.'''"""
|
||||
then:
|
||||
test.contains('assertThatJson(parsedJson).field("aBoolean").matches("(true|false)")')
|
||||
test.contains('assertThatJson(parsedJson).field("alpha").matches("[\\\\p{L}]*")')
|
||||
test.contains('assertThatJson(parsedJson).field("hostname").matches("((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?")')
|
||||
test.contains('assertThatJson(parsedJson).field("url").matches("((www\\\\.|(http|https|ftp|news|file)+\\\\:\\\\/\\\\/)[_.a-z0-9-]+\\\\.[a-z0-9\\\\/_:@=.+?,##%&~-]*[^.|\\\\\'|\\\\# |!|\\\\(|?|,| |>|<|;|\\\\)])")')
|
||||
test.contains('assertThatJson(parsedJson).field("hostname").matches("((http[s]?|ftp):/)/?([^:/\\\\s]+)(:[0-9]{1,5})?")')
|
||||
test.contains('assertThatJson(parsedJson).field("url").matches("^(?:(?:[A-Za-z][+-.\\\\w^_]*:/{2})?(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("number").matches("-?\\\\d*(\\\\.\\\\d+)?")')
|
||||
test.contains('assertThatJson(parsedJson).field("email").matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4}")')
|
||||
test.contains('assertThatJson(parsedJson).field("ip").matches("([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])")')
|
||||
@@ -1954,8 +1954,19 @@ World.'''"""
|
||||
test.contains('assertThatJson(parsedJson).field("nonEmptyString").matches(".+")')
|
||||
test.contains('assertThatJson(parsedJson).field("anyOf").matches("^foo' + endOfLineRegExSymbol + '|^bar' + endOfLineRegExSymbol + '")')
|
||||
!test.contains('cursor')
|
||||
!test.contains('REGEXP>>')
|
||||
and:
|
||||
SyntaxChecker.tryToCompile(methodBuilderName, blockBuilder.toString())
|
||||
and:
|
||||
String jsonSample = '''\
|
||||
String json = "{\\"duck\\":\\"8\\",\\"alpha\\":\\"YAJEOWYGMFBEWPMEMAZI\\",\\"number\\":-2095030871,\\"aBoolean\\":true,\\"ip\\":\\"129.168.99.100\\",\\"hostname\\":\\"http://foo389886219.com\\",\\"email\\":\\"foo@bar1367573183.com\\",\\"url\\":\\"http://foo-597104692.com\\",\\"uuid\\":\\"e436b817-b764-49a2-908e-967f2f99eb9f\\",\\"date\\":\\"2014-04-14\\",\\"dateTime\\":\\"2011-01-11T12:23:34\\",\\"time\\":\\"12:20:30\\",\\"iso8601WithOffset\\":\\"2015-05-15T12:23:34.123Z\\",\\"nonBlankString\\":\\"EPZWVIRHSUAPBJMMQSFO\\",\\"nonEmptyString\\":\\"RVMFDSEQFHRQFVUVQPIA\\",\\"anyOf\\":\\"foo\\"}";
|
||||
DocumentContext parsedJson = JsonPath.parse(json);
|
||||
'''
|
||||
and:
|
||||
LinkedList<String> lines = [] as LinkedList<String>
|
||||
test.eachLine { if (it.contains("assertThatJson")) lines << it else it }
|
||||
lines.addFirst(jsonSample)
|
||||
SyntaxChecker.tryToRun(methodBuilderName, lines.join("\n"))
|
||||
where:
|
||||
methodBuilderName | methodBuilder | endOfLineRegExSymbol
|
||||
"MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$'
|
||||
|
||||
@@ -763,6 +763,14 @@ class JsonToJsonPathsConverterSpec extends Specification {
|
||||
'$.a.b.c[?(@.d =~ /(.*)/)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.REGEX, jsonPath, regexPattern))
|
||||
}
|
||||
|
||||
def "should convert a json path with regex to a regex checking json path that has a / in it"() {
|
||||
given:
|
||||
String jsonPath = '$.a.b.c.d'
|
||||
String regexPattern = "/.*/"
|
||||
expect:
|
||||
'$.a.b.c[?(@.d =~ /(\\\\/.*\\\\/)/)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.REGEX, jsonPath, regexPattern))
|
||||
}
|
||||
|
||||
def "should convert a json path with value to a equality checking json path without quotes for numbers"() {
|
||||
given:
|
||||
String jsonPath = '$.a.b.c.d'
|
||||
|
||||
@@ -5,6 +5,9 @@ import org.codehaus.groovy.control.CompilerConfiguration
|
||||
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer
|
||||
import org.mdkt.compiler.InMemoryJavaCompiler
|
||||
import org.springframework.util.ReflectionUtils
|
||||
|
||||
import java.lang.reflect.Method
|
||||
|
||||
/**
|
||||
* checking the syntax of produced scripts
|
||||
@@ -51,6 +54,17 @@ class SyntaxChecker {
|
||||
}
|
||||
}
|
||||
|
||||
static void tryToRun(String builderName, String test) {
|
||||
if (builderName.toLowerCase().contains("spock")) {
|
||||
Script script = tryToCompileGroovy(test)
|
||||
script.run()
|
||||
} else {
|
||||
Class clazz = tryToCompileJava(test)
|
||||
Method method = ReflectionUtils.findMethod(clazz, "method")
|
||||
method.invoke(clazz.newInstance())
|
||||
}
|
||||
}
|
||||
|
||||
// no static compilation due to bug in Groovy https://issues.apache.org/jira/browse/GROOVY-8055
|
||||
static void tryToCompileWithoutCompileStatic(String builderName, String test) {
|
||||
if (builderName.toLowerCase().contains("spock")) {
|
||||
@@ -60,7 +74,7 @@ class SyntaxChecker {
|
||||
}
|
||||
}
|
||||
|
||||
static void tryToCompileGroovy(String test, boolean compileStatic = true) {
|
||||
static Script tryToCompileGroovy(String test, boolean compileStatic = true) {
|
||||
def imports = new ImportCustomizer()
|
||||
CompilerConfiguration configuration = new CompilerConfiguration()
|
||||
if (compileStatic) {
|
||||
@@ -75,7 +89,7 @@ class SyntaxChecker {
|
||||
sourceCode.append("WebTarget webTarget")
|
||||
sourceCode.append("\n")
|
||||
sourceCode.append(test)
|
||||
new GroovyShell(SyntaxChecker.classLoader, configuration).parse(sourceCode.toString())
|
||||
return new GroovyShell(SyntaxChecker.classLoader, configuration).parse(sourceCode.toString())
|
||||
}
|
||||
|
||||
static Class tryToCompileJava(String test) {
|
||||
@@ -89,11 +103,11 @@ class SyntaxChecker {
|
||||
sourceCode.append("${DEFAULT_IMPORTS_AS_STRING}\n")
|
||||
sourceCode.append("${STATIC_IMPORTS}\n")
|
||||
sourceCode.append("\n")
|
||||
sourceCode.append("class ${className} {\n")
|
||||
sourceCode.append("public class ${className} {\n")
|
||||
sourceCode.append("\n")
|
||||
sourceCode.append(" WebTarget webTarget;")
|
||||
sourceCode.append("\n")
|
||||
sourceCode.append(" void method() {\n")
|
||||
sourceCode.append(" public void method() {\n")
|
||||
sourceCode.append(" ${test}\n")
|
||||
sourceCode.append(" }\n")
|
||||
sourceCode.append("}")
|
||||
|
||||
Reference in New Issue
Block a user