Simplification of the dsl (#151)

with this change to DSL we're adding some helper methods and DSL simplifications 

- c(...) / p(...) - for consumer / producer
- for regex - e.g. $(anyUrl()) - insead of $(consumer(regex(url()))
- Added helper headers and media types
- Added helper http methods

fixes #149
This commit is contained in:
Marcin Grzejszczak
2016-11-18 15:38:35 +01:00
committed by GitHub
parent df80cc8f1f
commit b1175ff9bd
20 changed files with 1164 additions and 100 deletions

View File

@@ -29,4 +29,8 @@ class ClientDslProperty extends DslProperty {
ClientDslProperty(Object singleValue) {
super(singleValue)
}
ClientDslProperty(Object client, Object server) {
super(client, server)
}
}

View File

@@ -144,6 +144,17 @@ class Common {
return new ServerDslProperty(serverValue)
}
/**
* Helper method to provide a better name for the consumer side
*/
ClientDslProperty c(Object clientValue) {
return new ClientDslProperty(clientValue)
}
ServerDslProperty p(Object serverValue) {
return new ServerDslProperty(serverValue)
}
/**
* Helper method to provide a better name for the producer side
*/

View File

@@ -42,4 +42,10 @@ class DslProperty<T> {
this.clientValue = singleValue
this.serverValue = singleValue
}
boolean isSingleValue() {
return this.clientValue == this.serverValue ||
(this.clientValue != null && this.serverValue == null ) ||
(this.serverValue != null && this.clientValue == null )
}
}

View File

@@ -30,6 +30,9 @@ import groovy.transform.TypeChecked
@TypeChecked
class Headers {
@Delegate MediaTypes mediaTypes = new MediaTypes()
@Delegate HttpHeaders httpHeaders = new HttpHeaders()
Set<Header> entries = []
void header(Map<String, Object> singleHeader) {
@@ -47,6 +50,24 @@ class Headers {
}
}
void accept(String contentType) {
header(accept(), matching(contentType))
}
void contentType(String contentType) {
header(httpHeaders.contentType(), matching(contentType))
}
/**
* If for the consumer / producer you want to match exactly only
* the root of content type. I.e. {@code application/json;charset=UTF8}
* you care only about {@code application/json} then you should
* use this method
*/
DslProperty matching(String value) {
return new DslProperty(value)
}
/**
* Converts the headers into their stub side representations and returns as
* a map of String key => Object value.

View File

@@ -0,0 +1,480 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Contains most commonly used http headers
*
* @author Marcin Grzejszczak
* @since 1.0.2
*/
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class HttpHeaders {
/**
* The HTTP {@code Accept} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.2">Section 5.3.2 of RFC 7231</a>
*/
String accept() {
return "Accept"
}
/**
* The HTTP {@code Accept-Charset} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.3">Section 5.3.3 of RFC 7231</a>
*/
String acceptCharset() {
return "Accept-Charset"
}
/**
* The HTTP {@code Accept-Encoding} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.4">Section 5.3.4 of RFC 7231</a>
*/
String acceptEncoding() {
return "Accept-Encoding"
}
/**
* The HTTP {@code Accept-Language} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a>
*/
String acceptLanguage() {
return "Accept-Language"
}
/**
* The HTTP {@code Accept-Ranges} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
*/
String acceptRanges() {
return "Accept-Ranges"
}
/**
* The CORS {@code Access-Control-Allow-Credentials} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlAllowCredentials() {
return "Access-Control-Allow-Credentials"
}
/**
* The CORS {@code Access-Control-Allow-Headers} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlAllowHeaders() {
return "Access-Control-Allow-Headers"
}
/**
* The CORS {@code Access-Control-Allow-Methods} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlAllowMethods() {
return "Access-Control-Allow-Methods"
}
/**
* The CORS {@code Access-Control-Allow-Origin} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlAllowOrigin() {
return "Access-Control-Allow-Origin"
}
/**
* The CORS {@code Access-Control-Expose-Headers} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlExposeHeaders() {
return "Access-Control-Expose-Headers"
}
/**
* The CORS {@code Access-Control-Max-Age} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlMaxAge() {
return "Access-Control-Max-Age"
}
/**
* The CORS {@code Access-Control-Request-Headers} request header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlRequestHeaders() {
return "Access-Control-Request-Headers"
}
/**
* The CORS {@code Access-Control-Request-Method} request header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlRequestMethod() {
return "Access-Control-Request-Method"
}
/**
* The HTTP {@code Age} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.1">Section 5.1 of RFC 7234</a>
*/
String age() {
return "Age"
}
/**
* The HTTP {@code Allow} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.4.1">Section 7.4.1 of RFC 7231</a>
*/
String allow() {
return "Allow"
}
/**
* The HTTP {@code Authorization} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7235#section-4.2">Section 4.2 of RFC 7235</a>
*/
String authorization() {
return "Authorization"
}
/**
* The HTTP {@code Cache-Control} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.2">Section 5.2 of RFC 7234</a>
*/
String cacheControl() {
return "Cache-Control"
}
/**
* The HTTP {@code Connection} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-6.1">Section 6.1 of RFC 7230</a>
*/
String connection() {
return "Connection"
}
/**
* The HTTP {@code Content-Encoding} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.2.2">Section 3.1.2.2 of RFC 7231</a>
*/
String contentEncoding() {
return "Content-Encoding"
}
/**
* The HTTP {@code Content-Disposition} header field name
* @see <a href="http://tools.ietf.org/html/rfc6266">RFC 6266</a>
*/
String contentDisposition() {
return "Content-Disposition"
}
/**
* The HTTP {@code Content-Language} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.3.2">Section 3.1.3.2 of RFC 7231</a>
*/
String contentLanguage() {
return "Content-Language"
}
/**
* The HTTP {@code Content-Length} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.2">Section 3.3.2 of RFC 7230</a>
*/
String contentLength() {
return "Content-Length"
}
/**
* The HTTP {@code Content-Location} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.4.2">Section 3.1.4.2 of RFC 7231</a>
*/
String contentLocation() {
return "Content-Location"
}
/**
* The HTTP {@code Content-Range} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7233#section-4.2">Section 4.2 of RFC 7233</a>
*/
String contentRange() {
return "Content-Range"
}
/**
* The HTTP {@code Content-Type} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.5">Section 3.1.1.5 of RFC 7231</a>
*/
String contentType() {
return "Content-Type"
}
/**
* The HTTP {@code Cookie} header field name.
* @see <a href="http://tools.ietf.org/html/rfc2109#section-4.3.4">Section 4.3.4 of RFC 2109</a>
*/
String cookie() {
return "Cookie"
}
/**
* The HTTP {@code Date} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.1.2">Section 7.1.1.2 of RFC 7231</a>
*/
String date() {
return "Date"
}
/**
* The HTTP {@code ETag} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
*/
String etag() {
return "ETag"
}
/**
* The HTTP {@code Expect} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.1.1">Section 5.1.1 of RFC 7231</a>
*/
String expect() {
return "Expect"
}
/**
* The HTTP {@code Expires} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.3">Section 5.3 of RFC 7234</a>
*/
String expires() {
return "Expires"
}
/**
* The HTTP {@code From} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.1">Section 5.5.1 of RFC 7231</a>
*/
String from() {
return "From"
}
/**
* The HTTP {@code Host} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-5.4">Section 5.4 of RFC 7230</a>
*/
String host() {
return "Host"
}
/**
* The HTTP {@code If-Match} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-3.1">Section 3.1 of RFC 7232</a>
*/
String ifMatch() {
return "If-Match"
}
/**
* The HTTP {@code If-Modified-Since} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-3.3">Section 3.3 of RFC 7232</a>
*/
String ifModifiedSince() {
return "If-Modified-Since"
}
/**
* The HTTP {@code If-None-Match} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-3.2">Section 3.2 of RFC 7232</a>
*/
String ifNoneMatch() {
return "If-None-Match"
}
/**
* The HTTP {@code If-Range} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7233#section-3.2">Section 3.2 of RFC 7233</a>
*/
String ifRange() {
return "If-Range"
}
/**
* The HTTP {@code If-Unmodified-Since} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-3.4">Section 3.4 of RFC 7232</a>
*/
String ifUnmodifiedSince() {
return "If-Unmodified-Since"
}
/**
* The HTTP {@code Last-Modified} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-2.2">Section 2.2 of RFC 7232</a>
*/
String lastModified() {
return "Last-Modified"
}
/**
* The HTTP {@code Link} header field name.
* @see <a href="http://tools.ietf.org/html/rfc5988">RFC 5988</a>
*/
String link() {
return "Link"
}
/**
* The HTTP {@code Location} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.2">Section 7.1.2 of RFC 7231</a>
*/
String location() {
return "Location"
}
/**
* The HTTP {@code Max-Forwards} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.1.2">Section 5.1.2 of RFC 7231</a>
*/
String max_forwards() {
return "Max-Forwards"
}
/**
* The HTTP {@code Origin} header field name.
* @see <a href="http://tools.ietf.org/html/rfc6454">RFC 6454</a>
*/
String origin() {
return "Origin"
}
/**
* The HTTP {@code Pragma} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.4">Section 5.4 of RFC 7234</a>
*/
String pragma() {
return "Pragma"
}
/**
* The HTTP {@code Proxy-Authenticate} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7235#section-4.3">Section 4.3 of RFC 7235</a>
*/
String proxyAuthenticate() {
return "Proxy-Authenticate"
}
/**
* The HTTP {@code Proxy-Authorization} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7235#section-4.4">Section 4.4 of RFC 7235</a>
*/
String proxyAuthorization() {
return "Proxy-Authorization"
}
/**
* The HTTP {@code Range} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7233#section-3.1">Section 3.1 of RFC 7233</a>
*/
String range() {
return "Range"
}
/**
* The HTTP {@code Referer} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.2">Section 5.5.2 of RFC 7231</a>
*/
String referer() {
return "Referer"
}
/**
* The HTTP {@code Retry-After} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.3">Section 7.1.3 of RFC 7231</a>
*/
String retryAfter() {
return "Retry-After"
}
/**
* The HTTP {@code Server} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.4.2">Section 7.4.2 of RFC 7231</a>
*/
String server() {
return "Server"
}
/**
* The HTTP {@code Set-Cookie} header field name.
* @see <a href="http://tools.ietf.org/html/rfc2109#section-4.2.2">Section 4.2.2 of RFC 2109</a>
*/
String setCookie() {
return "Set-Cookie"
}
/**
* The HTTP {@code Set-Cookie2} header field name.
* @see <a href="http://tools.ietf.org/html/rfc2965">RFC 2965</a>
*/
String setCookie2() {
return "Set-Cookie2"
}
/**
* The HTTP {@code TE} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-4.3">Section 4.3 of RFC 7230</a>
*/
String te() {
return "TE"
}
/**
* The HTTP {@code Trailer} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-4.4">Section 4.4 of RFC 7230</a>
*/
String trailer() {
return "Trailer"
}
/**
* The HTTP {@code Transfer-Encoding} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.1">Section 3.3.1 of RFC 7230</a>
*/
String transferEncoding() {
return "Transfer-Encoding"
}
/**
* The HTTP {@code Upgrade} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-6.7">Section 6.7 of RFC 7230</a>
*/
String upgrade() {
return "Upgrade"
}
/**
* The HTTP {@code User-Agent} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.3">Section 5.5.3 of RFC 7231</a>
*/
String user_agent() {
return "User-Agent"
}
/**
* The HTTP {@code Vary} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.4">Section 7.1.4 of RFC 7231</a>
*/
String vary() {
return "Vary"
}
/**
* The HTTP {@code Via} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-5.7.1">Section 5.7.1 of RFC 7230</a>
*/
String via() {
return "Via"
}
/**
* The HTTP {@code Warning} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.5">Section 5.5 of RFC 7234</a>
*/
String warning() {
return "Warning"
}
/**
* The HTTP {@code WWW-Authenticate} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7235#section-4.1">Section 4.1 of RFC 7235</a>
*/
String wwwAuthenticate() {
return "WWW-Authenticate"
}
}

View File

@@ -0,0 +1,53 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Contains Http Methods
*
* @author Marcin Grzejszczak
* @since 1.0.2
*/
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class HttpMethods {
HttpMethod GET() {
return HttpMethod.GET
}
HttpMethod HEAD() {
return HttpMethod.HEAD
}
HttpMethod POST() {
return HttpMethod.POST
}
HttpMethod PUT() {
return HttpMethod.PUT
}
HttpMethod PATCH() {
return HttpMethod.PATCH
}
HttpMethod DELETE() {
return HttpMethod.DELETE
}
HttpMethod OPTIONS() {
return HttpMethod.OPTIONS
}
HttpMethod TRACE() {
return HttpMethod.TRACE
}
enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
}

View File

@@ -0,0 +1,85 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Contains most commonly used media types
*
* @author Marcin Grzejszczak
* @since 1.0.2
*/
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class MediaTypes {
String allValue() {
return "*/*"
}
String applicationAtomXml() {
return "application/atom+xml"
}
String applicationFormUrlencoded() {
return "application/x-www-form-urlencoded"
}
String applicationJson() {
return "application/json"
}
String applicationJsonUtf8() {
return applicationJson() + ";charset=UTF-8"
}
String applicationOctetStream() {
return "application/octet-stream"
}
String applicationPdf() {
return "application/pdf"
}
String applicationXhtmlXml() {
return "application/xhtml+xml"
}
String applicationXml() {
return "application/xml"
}
String imageGif() {
return "image/gif"
}
String imageJpeg() {
return "image/jpeg"
}
String imagePng() {
return "image/png"
}
String multipartFormData() {
return "multipart/form-data"
}
String textHtml() {
return "text/html"
}
String textMarkdown() {
return "text/markdown"
}
String textPlain() {
return "text/plain"
}
String textXml() {
return "text/xml"
}
}

View File

@@ -0,0 +1,82 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import java.util.regex.Pattern
/**
* @author Marcin Grzejszczak
*/
@PackageScope
@CompileStatic
abstract class PatternValueDslProperty<T extends DslProperty> {
private final Random random = new Random()
protected T createAndValidateProperty(Pattern pattern, Object object = null) {
if (object) {
String generatedValue = object as String
boolean matches = pattern.matcher(generatedValue).matches()
if (!matches) {
throw new IllegalStateException("The generated value [${generatedValue}] doesn't match the pattern [${pattern.pattern()}]")
}
return createProperty(pattern, generatedValue)
}
return createProperty(pattern, object)
}
/**
* Method to generate the PatternValue. The resulting implementation
* will create either a Client or a Server side impl.
*
* @param pattern - pattern for which the value will be generated or reused
* @param generatedValue - Nullable - potential generated value to be reused
* @return {@link DslProperty} wrapping a pattern and generated value
*/
protected abstract T createProperty(Pattern pattern, Object generatedValue)
T anyAlphaUnicode() {
return createAndValidateProperty(RegexPatterns.ONLY_ALPHA_UNICODE, randomString(20))
}
T anyNumber() {
return createAndValidateProperty(RegexPatterns.NUMBER, this.random.nextInt())
}
T aBoolean() {
return createAndValidateProperty(RegexPatterns.TRUE_OR_FALSE)
}
T anyIpAddress() {
return createAndValidateProperty(RegexPatterns.IP_ADDRESS)
}
T anyHostname() {
return createAndValidateProperty(RegexPatterns.HOSTNAME_PATTERN, "http://foo" + this.random.nextInt() + ".com")
}
T anyEmail() {
return createAndValidateProperty(RegexPatterns.EMAIL, "foo@bar" + this.random.nextInt() + ".com")
}
T anyUrl() {
return createAndValidateProperty(RegexPatterns.URL, "http://foo" + this.random.nextInt() + ".com")
}
T anyUuid(){
return createAndValidateProperty(RegexPatterns.UUID, UUID.randomUUID().toString())
}
private static String randomString(int length) {
char[] characterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()
Random random = new Random()
char[] result = new char[length]
for (int i = 0; i < result.length; i++) {
// picks a random index out of character set > random character
int randomCharIndex = random.nextInt(characterSet.length)
result[i] = characterSet[randomCharIndex]
}
return new String(result)
}
}

View File

@@ -19,7 +19,6 @@ package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import java.util.regex.Pattern
/**
* Contains most common regular expression patterns
*
@@ -29,14 +28,14 @@ import java.util.regex.Pattern
class RegexPatterns {
// tag::regexps[]
private static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
private static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
private static final Pattern NUMBER = Pattern.compile('-?\\d*(\\.\\d+)?')
private 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])');
private static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?');
private static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}');
private static final Pattern URL = Pattern.compile('((www\\.|(http|https|ftp|news|file)+\\:\\/\\/)[_.a-z0-9-]+\\.[a-z0-9\\/_:@=.+?,##%&~-]*[^.|\\\'|\\# |!|\\(|?|,| |>|<|;|\\)])')
private 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 TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
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 UUID = Pattern.compile('[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}')
String onlyAlphaUnicode() {
return ONLY_ALPHA_UNICODE.pattern()

View File

@@ -20,6 +20,7 @@ import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import org.springframework.cloud.contract.spec.util.RegexpUtils
import repackaged.nl.flotsam.xeger.Xeger
import java.util.regex.Pattern
@@ -33,10 +34,13 @@ import java.util.regex.Pattern
@ToString(includePackage = false, includeNames = true)
class Request extends Common {
@Delegate ClientPatternValueDslProperty property = new ClientPatternValueDslProperty()
@Delegate HttpMethods httpMethods = new HttpMethods()
DslProperty method
Url url
UrlPath urlPath
Headers headers
RequestHeaders headers
Body body
Multipart multipart
@@ -56,6 +60,10 @@ class Request extends Common {
this.method = toDslProperty(method)
}
void method(HttpMethods.HttpMethod httpMethod) {
this.method = toDslProperty(httpMethod.toString())
}
void method(DslProperty method) {
this.method = toDslProperty(method)
}
@@ -100,8 +108,8 @@ class Request extends Common {
closure()
}
void headers(@DelegatesTo(Headers) Closure closure) {
this.headers = new Headers()
void headers(@DelegatesTo(RequestHeaders) Closure closure) {
this.headers = new RequestHeaders()
closure.delegate = headers
closure()
}
@@ -176,8 +184,10 @@ class Request extends Common {
DslProperty value(ClientDslProperty client) {
Object clientValue = client.clientValue
if (client.clientValue instanceof Pattern) {
if (client.clientValue instanceof Pattern && client.isSingleValue()) {
clientValue = new Xeger(((Pattern)client.clientValue).pattern()).generate()
} else if (client.clientValue instanceof Pattern && !client.isSingleValue()) {
clientValue = client.serverValue
}
return new DslProperty(client.clientValue, clientValue)
}
@@ -202,22 +212,44 @@ class Request extends Common {
return super.value(server, client)
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class ServerRequest extends Request {
ServerRequest(Request request) {
super(request)
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ServerRequest extends Request {
ServerRequest(Request request) {
super(request)
}
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class ClientRequest extends Request {
ClientRequest(Request request) {
super(request)
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ClientRequest extends Request {
ClientRequest(Request request) {
super(request)
}
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class RequestHeaders extends Headers {
@Override
DslProperty matching(String value) {
return $(c(regex("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*")),
p(value))
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ClientPatternValueDslProperty extends PatternValueDslProperty<ClientDslProperty> {
@Override
protected ClientDslProperty createProperty(Pattern pattern, Object generatedValue) {
return new ClientDslProperty(pattern, generatedValue)
}
}
}

View File

@@ -20,10 +20,10 @@ import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import org.springframework.cloud.contract.spec.util.RegexpUtils
import repackaged.nl.flotsam.xeger.Xeger
import java.util.regex.Pattern
/**
* Represents the response side of the HTTP communication
*
@@ -34,9 +34,11 @@ import java.util.regex.Pattern
@ToString(includePackage = false, includeFields = true)
class Response extends Common {
@Delegate ServerPatternValueDslProperty property = new ServerPatternValueDslProperty()
DslProperty status
DslProperty delay
Headers headers
ResponseHeaders headers
Body body
boolean async
@@ -57,8 +59,8 @@ class Response extends Common {
this.status = toDslProperty(status)
}
void headers(@DelegatesTo(Headers) Closure closure) {
this.headers = new Headers()
void headers(@DelegatesTo(ResponseHeaders) Closure closure) {
this.headers = new ResponseHeaders()
closure.delegate = headers
closure()
}
@@ -89,8 +91,10 @@ class Response extends Common {
DslProperty value(ServerDslProperty server) {
Object value = server.clientValue
if (server.clientValue instanceof Pattern) {
if (server.clientValue instanceof Pattern && server.isSingleValue()) {
value = new Xeger(((Pattern)server.clientValue).pattern()).generate()
} else if (server.clientValue instanceof Pattern && !server.isSingleValue()) {
value = server.serverValue
}
return new DslProperty(value, server.serverValue)
}
@@ -114,22 +118,46 @@ class Response extends Common {
}
return super.value(server, client)
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class ServerResponse extends Response {
ServerResponse(Response request) {
super(request)
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ServerResponse extends Response {
ServerResponse(Response request) {
super(request)
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ClientResponse extends Response {
ClientResponse(Response request) {
super(request)
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ResponseHeaders extends Headers {
@Override
DslProperty matching(String value) {
return $(p(Pattern.compile("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*")),
c(value))
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ServerPatternValueDslProperty extends PatternValueDslProperty<ServerDslProperty> {
@Override
protected ServerDslProperty createProperty(Pattern pattern, Object generatedValue) {
return new ServerDslProperty(pattern, generatedValue)
}
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class ClientResponse extends Response {
ClientResponse(Response request) {
super(request)
}
}

View File

@@ -33,4 +33,8 @@ class ServerDslProperty extends DslProperty {
ServerDslProperty(Object singleValue) {
super(singleValue)
}
ServerDslProperty(Object server, Object client) {
super(client, server)
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.spec.util
import groovy.transform.CompileStatic
import java.util.regex.Pattern
/**
* Useful utility methods to work with regular expresisons
*
* @since 1.0.2
*/
@CompileStatic
class RegexpUtils {
private final static Pattern SPECIAL_REGEX_CHARS = Pattern.compile('[{}()\\[\\].+*?^$\\\\|]')
static String escapeSpecialRegexChars(String str) {
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll('\\\\\\\\$0')
}
static String escapeSpecialRegexWithSingleEscape(String str) {
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll('\\\\$0')
}
}

View File

@@ -9,18 +9,18 @@ org.springframework.cloud.contract.spec.Contract.make {
"""
)
headers {
header("""Content-Type""", """application/vnd.fraud.v1+json""")
contentType("application/vnd.fraud.v1+json")
}
}
response {
status 200
body( """{
"fraudCheckStatus": "${value(consumer('FRAUD'), producer(regex('[A-Z]{5}')))}",
"fraudCheckStatus": "${value(c('FRAUD'), p(regex('[A-Z]{5}')))}",
"rejectionReason": "Amount too high"
}""")
headers {
header('Content-Type': value(producer(regex('application/vnd.fraud.v1.json.*')), consumer('application/vnd.fraud.v1+json')))
contentType("application/vnd.fraud.v1+json")
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.contract.verifier.util
import groovy.transform.TypeChecked
import org.codehaus.groovy.runtime.GStringImpl
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.util.RegexpUtils
import java.util.regex.Pattern
@@ -97,10 +98,8 @@ class RegexpBuilders {
return o.toString().replaceAll('\\\\', '\\\\\\\\')
}
private final static Pattern SPECIAL_REGEX_CHARS = Pattern.compile('[{}()\\[\\].+*?^$\\\\|]')
static String escapeSpecialRegexChars(String str) {
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll('\\\\\\\\$0')
return RegexpUtils.escapeSpecialRegexChars(str)
}
private final static String WS = /\s*/

View File

@@ -138,8 +138,10 @@ class ContractHttpDocsSpec extends Specification {
//...
// Each header is added in form `'Header-Name' : 'Header-Value'`.
// there are also some helper methods
headers {
header 'Content-Type': 'application/json'
header 'key': 'value'
contentType(applicationJson())
}
//...
@@ -210,10 +212,7 @@ class ContractHttpDocsSpec extends Specification {
response {
status 200
body(
id: value(
consumer('123'),
producer(regex('[0-9]+'))
),
id: $(anyNumber()),
surname: $(
consumer('Kowalsky'),
producer(regex('[a-zA-Z]+'))
@@ -239,7 +238,7 @@ class ContractHttpDocsSpec extends Specification {
method 'POST'
url '/users/password'
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),

View File

@@ -339,7 +339,6 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub
)
headers {
header('Content-Type': 'application/json')
}
}
@@ -371,9 +370,7 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub
body("""{"property1":"a","property2":"${value(consumer('123'), producer(regex('[0-9]{3}')))}"}""")
headers {
header('Content-Type': 'application/json')
}
}
}
MethodBodyBuilder builder = methodBuilder(contractDsl)
@@ -628,7 +625,7 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub
response {
status 200
headers {
header('Content-Type': 'application/json;charset=UTF-8')
contentType(applicationJson())
}
body """
{"id":"789fgh","other_data":1268}
@@ -766,6 +763,29 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub
test.contains("assertThat(responseBody).matches(\".*\");")
}
@Issue('#150')
def "should support body matching in response in Spock"() {
given:
Contract contractDsl = Contract.make {
request {
method 'GET'
url '/get'
}
response {
status 200
status 200
body(value(stub("HELLO FROM STUB"), server(regex(".*"))))
}
}
MethodBodyBuilder builder = new JaxRsClientSpockMethodRequestProcessingBodyBuilder(contractDsl, properties)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.then(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains("responseBody ==~ java.util.regex.Pattern.compile('.*')")
}
@Issue('#150')
def "should support custom method execution in response"() {
given:
@@ -789,6 +809,125 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub
test.contains("foo(responseBody);")
}
@Issue('#150')
def "should support custom method execution in response in Spock"() {
given:
Contract contractDsl = Contract.make {
request {
method 'GET'
url '/get'
}
response {
status 200
status 200
body(value(stub("HELLO FROM STUB"), server(execute('foo($it)'))))
}
}
MethodBodyBuilder builder = new JaxRsClientSpockMethodRequestProcessingBodyBuilder(contractDsl, properties)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.then(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains("foo(responseBody)")
}
def "should allow c/p version of consumer producer"() {
given:
Contract contractDsl = Contract.make {
request {
method "GET"
url "test"
}
response {
status 200
body(
property1: "a",
property2: $(
c('123'),
p(regex('[0-9]{3}'))
)
)
headers {
header('Content-Type': 'application/json')
}
}
}
MethodBodyBuilder builder = methodBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").matches("[0-9]{3}")""")
blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""")
and:
stubMappingIsValidWireMockStub(contractDsl)
where:
methodBuilderName | methodBuilder
"JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
"JaxRsClientJUnitMethodBodyBuilder" | { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) }
}
@Issue('#149')
def "should allow easier way of providing dynamic values"() {
given:
Contract contractDsl = Contract.make {
request {
method 'GET'
urlPath '/get'
body([
alpha: $(anyAlphaUnicode()),
number: $(anyNumber()),
aBoolean: $(aBoolean()),
ip: $(anyIpAddress()),
hostname: $(anyHostname()),
email: $(anyEmail()),
url: $(anyUrl()),
uuid: $(anyUuid())
])
headers {
contentType(applicationJson())
}
}
response {
status 200
body([
alpha: $(anyAlphaUnicode()),
number: $(anyNumber()),
aBoolean: $(aBoolean()),
ip: $(anyIpAddress()),
hostname: $(anyHostname()),
email: $(anyEmail()),
url: $(anyUrl()),
uuid: $(anyUuid())
])
headers {
contentType(applicationJson())
}
}
}
MethodBodyBuilder builder = methodBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
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("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])")')
test.contains('assertThatJson(parsedJson).field("uuid").matches("[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}")')
!test.contains('cursor')
where:
methodBuilderName | methodBuilder
"JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
"JaxRsClientJUnitMethodBodyBuilder" | { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) }
}
private String stripped(String string) {
return string.stripMargin().stripIndent().replace('\t', '').replace('\n', '').replace(' ','')
}

View File

@@ -40,7 +40,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
method 'POST'
url '/users/password'
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
@@ -50,7 +50,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
response {
status 404
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
code: value(consumer("123123"), producer(optional("123123"))),
@@ -67,7 +67,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
method 'POST'
url '/users/password'
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
""" {
@@ -84,7 +84,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
response {
status 404
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
""" {
@@ -102,7 +102,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
given:
Contract contractDsl = Contract.make {
request {
method "GET"
method GET()
url "test"
}
response {
@@ -133,7 +133,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
given:
Contract contractDsl = Contract.make {
request {
method "GET"
method GET()
url "test"
}
response {
@@ -439,7 +439,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
)
)
headers {
header('Content-Type': 'application/json')
contentType(applicationJson())
}
}
}
@@ -471,7 +471,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
value(consumer('123'), producer(regex('[0-9]{3}')))
}"}""")
headers {
header('Content-Type': 'application/json')
contentType(applicationJson())
}
}
}
@@ -504,7 +504,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
value(consumer('123'), producer(regex('\\d+')))
}"}""")
headers {
header('Content-Type': 'application/json')
contentType(applicationJson())
}
}
}
@@ -621,7 +621,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
given:
Contract contractDsl = Contract.make {
request {
method('POST')
method(POST())
url("/ws/payments")
body("")
}
@@ -679,7 +679,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
request {
method 'POST'
url $(consumer(regex('/partners/[0-9]+/users')), producer('/partners/1000/users'))
headers { header 'Content-Type': 'application/json' }
headers { contentType(applicationJson()) }
body(
first_name: 'John',
last_name: 'Smith',
@@ -718,7 +718,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
request {
method 'POST'
url $(consumer(regex('/partners/[0-9]+/users')), producer('/partners/1000/users'))
headers { header 'Content-Type': 'application/json' }
headers { contentType(applicationJson()) }
body(
first_name: 'John',
last_name: 'Smith',
@@ -758,7 +758,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
method 'POST'
url '/validation/client'
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
bank_account_number: '0014282912345698765432161182',
@@ -796,10 +796,10 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
Contract contractDsl = Contract.make {
request {
method 'PUT'
method PUT()
url "/partners/${value(consumer(regex('^[0-9]*$')), producer('11'))}/agents/11/customers/09665703Z"
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
first_name: 'Josef',
@@ -829,10 +829,10 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
Contract contractDsl = Contract.make {
priority 1
request {
method 'POST'
method POST()
url '/users/password'
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
email: $(consumer(regex(email())), producer('not.existing@user.com')),
@@ -842,7 +842,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
response {
status 404
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body(
code: 4,
@@ -965,7 +965,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
method "PUT"
url "/v1/payments/e86df6f693de4b35ae648464c5b0dc09/client_data"
headers {
header('Content-Type': 'application/json')
contentType(applicationJson())
}
body(
client: [
@@ -1000,7 +1000,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
response {
status 200
headers {
header('Content-Type': 'application/json')
contentType(applicationJson())
}
}
}
@@ -1029,7 +1029,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
method "PUT"
url "/v1/payments/e86df6f693de4b35ae648464c5b0dc09/енев"
headers {
header('Content-Type': 'application/json')
contentType(applicationJson())
}
body(
client: [
@@ -1041,7 +1041,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
response {
status 200
headers {
header('Content-Type': 'application/json')
contentType(applicationJson())
}
}
}
@@ -1094,7 +1094,7 @@ World.'''"""
method "PUT"
url "/multipart"
headers {
header('content-type', 'multipart/form-data;boundary=AaB03x')
contentType('multipart/form-data;boundary=AaB03x')
}
multipart(
formParameter: value(consumer(regex('.+')), producer('"formParameterValue"')),
@@ -1275,7 +1275,7 @@ World.'''"""
producer(regex('[0-9]+'))
)]])
headers {
header('Content-Type': 'application/json;charset=UTF-8')
contentType(applicationJsonUtf8())
}
}
}
@@ -1525,22 +1525,19 @@ World.'''"""
method 'PUT'
url value(consumer(regex('/foo/[0-9]{5}')))
body([
requestElement: value(consumer(regex('[0-9]{5}')))
requestElement: $(consumer(regex('[0-9]{5}')))
])
headers {
header('header', value(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*'))))
header('header', $(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*'))))
}
}
response {
status 200
body([
responseElement: value(producer(regex('[0-9]{7}')))
responseElement: $(producer(regex('[0-9]{7}')))
])
headers {
header('Content-Type': value(
producer(regex('application/vnd.fraud.v1.json.*')),
consumer('application/vnd.fraud.v1+json'))
)
contentType("application/vnd.fraud.v1.json")
}
}
}
@@ -1560,7 +1557,7 @@ World.'''"""
strippedTest.matches(""".*header\\("header", "application\\/vnd\\.fraud\\.v1\\+json;.*"\\).*""")
strippedTest.matches(""".*body\\('''\\{"requestElement":"[0-9]{5}"\\}'''\\).*""")
strippedTest.matches(""".*put\\("/foo/[0-9]{5}"\\).*""")
strippedTest.contains("""response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/vnd.fraud.v1.json.*')""")
strippedTest.contains("""response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/vnd\\.fraud\\.v1\\.json.*')""")
"application/vnd.fraud.v1+json;charset=UTF-8".matches('application/vnd.fraud.v1.json.*')
strippedTest.contains("""assertThatJson(parsedJson).field("responseElement").matches("[0-9]{7}")""")
}
@@ -1668,4 +1665,88 @@ World.'''"""
test.contains("foo(responseBody)")
}
@Issue('#149')
def "should allow c/p version of consumer producer"() {
given:
Contract contractDsl = Contract.make {
request {
method 'GET'
urlPath '/get'
headers {
header('authorization', $(c('Bearer token'), p(execute('getOAuthTokenHeader()'))))
}
}
response {
status 200
body([
fraudCheckStatus: "OK",
rejectionReason : [
title: $(c(null), p(execute('assertThatRejectionReasonIsNull($it)')))
]
])
}
}
MethodBodyBuilder builder = new MockMvcSpockMethodRequestProcessingBodyBuilder(contractDsl, properties)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.given(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains('.header("authorization", getOAuthTokenHeader())')
}
@Issue('#149')
def "should allow easier way of providing dynamic values"() {
given:
Contract contractDsl = Contract.make {
request {
method 'GET'
urlPath '/get'
body([
alpha: $(anyAlphaUnicode()),
number: $(anyNumber()),
aBoolean: $(aBoolean()),
ip: $(anyIpAddress()),
hostname: $(anyHostname()),
email: $(anyEmail()),
url: $(anyUrl()),
uuid: $(anyUuid())
])
headers {
contentType(applicationJson())
}
}
response {
status 200
body([
alpha: $(anyAlphaUnicode()),
number: $(anyNumber()),
aBoolean: $(aBoolean()),
ip: $(anyIpAddress()),
hostname: $(anyHostname()),
email: $(anyEmail()),
url: $(anyUrl()),
uuid: $(anyUuid())
])
headers {
contentType(applicationJson())
}
}
}
MethodBodyBuilder builder = new MockMvcSpockMethodRequestProcessingBodyBuilder(contractDsl, properties)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
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("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])")')
test.contains('assertThatJson(parsedJson).field("uuid").matches("[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}")')
!test.contains('cursor')
}
}

View File

@@ -20,7 +20,7 @@ Contract.make {
request {
method('PUT')
headers {
header 'Content-Type': 'application/json'
contentType(applicationJson())
}
body("""\
{
@@ -42,7 +42,7 @@ Contract.make {
"""
)
headers {
header 'Content-Type': 'text/plain'
contentType(textPlain())
}
}
}

View File

@@ -94,7 +94,7 @@ class CamelMessagingApplicationSpec extends Specification {
])
messageHeaders {
header('sample', 'header')
header('Content-Type', 'application/json')
}
}
outputMessage {