Body matching by content type and client-side XML support

This commit is contained in:
Denis Stepanov
2015-06-12 11:47:15 +02:00
parent 05f039ff7a
commit 118b63c2a0
14 changed files with 897 additions and 242 deletions

View File

@@ -85,7 +85,7 @@ class DslToWiremockClientConverterSpec extends Specification {
"method":"PUT",
"url":"/api/12",
"bodyPatterns": [
{ "equalTo": "[{\\"created_at\\":\\"Sat Jul 26 09:38:57 +0000 2014\\",\\"id\\":492967299297845248,\\"id_str\\":\\"492967299297845248\\",\\"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\\"},\\"text\\":\\"Gonna see you at Warsaw\\"}]" }
{ "equalToJson": "[{\\"created_at\\":\\"Sat Jul 26 09:38:57 +0000 2014\\",\\"id\\":492967299297845248,\\"id_str\\":\\"492967299297845248\\",\\"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\\"},\\"text\\":\\"Gonna see you at Warsaw\\"}]" }
],
"headers": {
"Content-Type": {

View File

@@ -11,8 +11,14 @@ import io.codearte.accurest.dsl.internal.QueryParameter
import io.codearte.accurest.dsl.internal.Request
import io.codearte.accurest.dsl.internal.Response
import io.codearte.accurest.dsl.internal.UrlPath
import io.codearte.accurest.util.ContentType
import java.util.regex.Pattern
import static io.codearte.accurest.util.ContentUtils.extractValue
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromHeader
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromContent
/**
* @author Jakub Kubrynski
*/
@@ -36,7 +42,11 @@ class SpockMethodBodyBuilder {
addLine(".header('${header.name}', '${header.serverValue}')")
}
if (request.body) {
String matches = new JsonOutput().toJson(request.body.serverValue)
Object bodyValue = request.body.serverValue
if (bodyValue instanceof GString) {
bodyValue = extractValue(bodyValue, {DslProperty dslProperty -> dslProperty.serverValue})
}
String matches = new JsonOutput().toJson(bodyValue)
addLine(".body('$matches')")
}
@@ -61,12 +71,24 @@ class SpockMethodBodyBuilder {
if (response.body) {
endBlock()
addLine('and:').startBlock()
addLine('def responseBody = new JsonSlurper().parseText(response.body.asString())')
def responseBody = response.body.serverValue
if (responseBody instanceof List) {
processArrayElements(responseBody, "", blockBuilder)
} else {
processMapElement(responseBody, blockBuilder, "")
ContentType contentType = recognizeContentTypeFromHeader(response.headers)
if (contentType == ContentType.UNKNOWN) {
contentType = recognizeContentTypeFromContent(responseBody)
}
if (responseBody instanceof GString) {
responseBody = extractValue(responseBody, contentType, { DslProperty dslProperty -> dslProperty.serverValue })
}
if (contentType == ContentType.JSON) {
addLine('def responseBody = new JsonSlurper().parseText(response.body.asString())')
if (responseBody instanceof List) {
processArrayElements(responseBody, "", blockBuilder)
} else {
processMapElement(responseBody, blockBuilder, "")
}
} else if (contentType == ContentType.XML) {
addLine('def responseBody = new XmlSlurper().parseText(response.body.asString())')
// TODO xml validation
}
}
endBlock()

View File

@@ -1,22 +1,22 @@
package io.codearte.accurest.dsl
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
import groovy.transform.TypeChecked
import groovy.xml.XmlUtil
import io.codearte.accurest.dsl.internal.DslProperty
import io.codearte.accurest.dsl.internal.Header
import io.codearte.accurest.dsl.internal.Headers
import io.codearte.accurest.util.JsonConverter
import io.codearte.accurest.util.ContentType
import java.util.regex.Pattern
import static groovy.json.StringEscapeUtils.escapeJava
import static io.codearte.accurest.util.ContentUtils.extractValue
import static io.codearte.accurest.util.JsonConverter.transformValues
@TypeChecked
abstract class BaseWiremockStubStrategy {
private static Closure transform = {
it instanceof DslProperty ? JsonConverter.transformValues(it.clientValue, transform) : it
it instanceof DslProperty ? transformValues(it.clientValue, transform) : it
}
protected Map buildClientRequestHeadersSection(Headers headers) {
@@ -33,43 +33,49 @@ abstract class BaseWiremockStubStrategy {
return null
}
return headers.entries.collectEntries { Header entry ->
[(entry.name) : entry.clientValue]
[(entry.name): entry.clientValue]
}
}
protected Map parseHeader(String entryKey, Object entry) {
return [(entryKey): [equalTo : entry]]
return [(entryKey): [equalTo: entry]]
}
protected Map parseHeader(String entryKey, String entry) {
return [(entryKey): [equalTo : entry]]
return [(entryKey): [equalTo: entry]]
}
protected Map parseHeader(String entryKey, Pattern entry) {
return [(entryKey): [matches : entry.pattern()]]
return [(entryKey): [matches: entry.pattern()]]
}
protected String parseBody(Object body) {
String bodyAsString = body as String
try {
def json = new JsonSlurper().parseText(bodyAsString)
return escapeJava(JsonOutput.toJson(bodyAsString))
} catch (Exception jsonException) {
try {
def xml = new XmlSlurper().parseText(bodyAsString)
return escapeJava(XmlUtil.serialize(bodyAsString))
} catch (Exception xmlException) {
return escapeJava(bodyAsString)
}
}
}
public String parseBody(Object value, ContentType contentType) {
return parseBody(value.toString(), contentType)
}
protected String parseBody(List body) {
return JsonOutput.toJson(body)
}
public String parseBody(Map map, ContentType contentType) {
def transformedMap = transformValues(map, transform)
return parseBody(toJson(transformedMap), contentType)
}
protected String parseBody(Map body) {
def transformedMap = JsonConverter.transformValues(body, transform)
return JsonOutput.toJson(transformedMap)
}
}
public String parseBody(List list, ContentType contentType) {
return parseBody(toJson(list), contentType)
}
public String parseBody(GString value, ContentType contentType) {
Object processedValue = extractValue(value, contentType, { DslProperty dslProperty -> dslProperty.clientValue })
if (processedValue instanceof GString) {
return parseBody(processedValue.toString(), contentType)
}
return parseBody(processedValue, contentType)
}
public String parseBody(String value, ContentType contentType) {
return value
}
private static toJson(Object value) {
return new JsonBuilder(value).toString()
}
}

View File

@@ -1,16 +1,23 @@
package io.codearte.accurest.dsl
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import io.codearte.accurest.dsl.internal.Body
import io.codearte.accurest.dsl.internal.ClientRequest
import io.codearte.accurest.dsl.internal.DslProperty
import io.codearte.accurest.dsl.internal.MatchingStrategy
import io.codearte.accurest.dsl.internal.QueryParameter
import io.codearte.accurest.dsl.internal.QueryParameters
import io.codearte.accurest.dsl.internal.Request
import io.codearte.accurest.util.ContentType
import java.util.regex.Pattern
import static io.codearte.accurest.dsl.internal.JsonStructureConverter.TEMPORARY_PATTERN_HOLDER
import static io.codearte.accurest.dsl.internal.JsonStructureConverter.convertJsonStructureToObjectUnderstandingStructure
import static io.codearte.accurest.util.ContentUtils.extractValue
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromHeader
import static io.codearte.accurest.util.ContentUtils.getEqualsTypeFromContentType
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromContent
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromMatchingStrategy
@TypeChecked
@PackageScope
@@ -29,7 +36,7 @@ class WiremockRequestStubStrategy extends BaseWiremockStubStrategy {
private Map<String, Object> buildRequestContent(ClientRequest request) {
return ([method : request?.method?.clientValue,
headers : buildClientRequestHeadersSection(request.headers)
headers : buildClientRequestHeadersSection(request.headers)
] << appendUrl(request) << appendQueryParameters(request) << appendBody(request)).findAll { it.value }
}
@@ -75,40 +82,69 @@ class WiremockRequestStubStrategy extends BaseWiremockStubStrategy {
}
private Map<String, Object> appendBody(ClientRequest clientRequest) {
Object body = clientRequest?.body?.clientValue
if (body == null) {
return [:]
return clientRequest.body? appendBody(clientRequest.body) : [:]
}
private Map<String, Object> appendBody(Body body) {
return [bodyPatterns: (appendBodyPatterns(body.clientValue))]
}
private List<Map<String, Object>> appendBodyPatterns(MatchingStrategy matchingStrategy) {
return [appendBodyPattern(matchingStrategy)]
}
private List<Map<String, Object>> appendBodyPatterns(List<MatchingStrategy> matchingStrategies) {
return matchingStrategies.collect { appendBodyPattern(it) }
}
private List<Map<String, Object>> appendBodyPatterns(GString gString) {
if (containsPattern(gString)) {
Object value = extractValue(gString, { DslProperty dslProperty -> dslProperty.clientValue })
return appendBodyPatterns(extractReqexpMatching(value))
}
if (containsRegex(body)) {
return [bodyPatterns: [[matches: parseBody(convertJsonStructureToObjectUnderstandingStructure(body,
{ it instanceof Pattern },
{ String json -> json.collect {
switch(it) {
case ('{'): return '\\{'
case ('}'): return '\\}'
default: return it
}
} .join('')
},
{ LinkedList list, String json ->
return json.replaceAll(TEMPORARY_PATTERN_HOLDER, { String a, String[] b -> list.pop() })
}
))]]]
return appendBodyPatterns(new MatchingStrategy(gString, getEqualsTypeFromContentTypeHeader()))
}
private List<Map<String, Object>> appendBodyPatterns(Object bodyValue) {
return appendBodyPatterns(new MatchingStrategy(bodyValue, MatchingStrategy.Type.EQUAL_TO))
}
private Map<String, Object> appendBodyPattern(MatchingStrategy matchingStrategy) {
MatchingStrategy.Type type = matchingStrategy.type
Object value= matchingStrategy.clientValue
ContentType contentType = recognizeContentTypeFromMatchingStrategy(type)
if (contentType == ContentType.UNKNOWN && type == MatchingStrategy.Type.EQUAL_TO) {
contentType = recognizeContentTypeFromContent(value)
type = getEqualsTypeFromContentType(contentType)
}
return [bodyPatterns: [[equalTo: parseBody(body)]]]
Map<String, ? extends Object> result = [(type.name): parseBody(value, contentType)]
if (type == MatchingStrategy.Type.EQUAL_TO_JSON && matchingStrategy.jsonCompareMode) {
return result << [jsonCompareMode : (matchingStrategy.jsonCompareMode.toString())]
}
return result
}
protected String parseBody(Object body) {
return body
private boolean containsPattern(GString bodyAsValue) {
return bodyAsValue.values.collect { it instanceof DslProperty ? it.clientValue : it }
.find { it instanceof Pattern }
}
boolean containsRegex(Object bodyObject) {
String bodyString = bodyObject as String
return (bodyString =~ /\^.*\$/).find()
private List<MatchingStrategy> extractReqexpMatching(Object responseBodyObject) {
def matchingStrategies = new ArrayList<MatchingStrategy>()
responseBodyObject.each { k, v ->
if (v instanceof List) {
v.each {
matchingStrategies.addAll(extractReqexpMatching((Map<String, Object>)it))
}
} else {
matchingStrategies.add(new MatchingStrategy(/.*${k}":.?"?${v}"?.*/, MatchingStrategy.Type.MATCHING))
}
}
return matchingStrategies
}
boolean containsRegex(Map map) {
return map.values().any { it instanceof Pattern }
private MatchingStrategy.Type getEqualsTypeFromContentTypeHeader() {
return getEqualsTypeFromContentType(recognizeContentTypeFromHeader(request.headers))
}
}

View File

@@ -2,16 +2,23 @@ package io.codearte.accurest.dsl
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import io.codearte.accurest.dsl.internal.ClientResponse
import io.codearte.accurest.dsl.internal.Request
import io.codearte.accurest.dsl.internal.Response
import io.codearte.accurest.util.ContentType
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromContent
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromHeader
@TypeChecked
@PackageScope
class WiremockResponseStubStrategy extends BaseWiremockStubStrategy {
private final Request request
private final Response response
WiremockResponseStubStrategy(GroovyDsl groovyDsl) {
this.response = groovyDsl.response
this.request = groovyDsl.request
}
@PackageScope
@@ -27,6 +34,12 @@ class WiremockResponseStubStrategy extends BaseWiremockStubStrategy {
private Map<String, Object> appendBody(ClientResponse response) {
Object body = response?.body?.clientValue
return body != null ? [body: parseBody(body)] : [:]
ContentType contentType = recognizeContentTypeFromHeader(response.headers)
if (contentType == ContentType.UNKNOWN) {
contentType = recognizeContentTypeFromContent(body)
}
return body != null ? [body: parseBody(body, contentType)] : [:]
}
}

View File

@@ -1,23 +1,16 @@
package io.codearte.accurest.dsl.internal
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import io.codearte.accurest.util.JsonConverter
import org.codehaus.groovy.runtime.GStringImpl
import java.util.regex.Matcher
import java.util.regex.Pattern
@ToString(includePackage = false, includeFields = true, includeNames = true)
@EqualsAndHashCode(includeFields = true)
@CompileStatic
class Body extends DslProperty {
private static final Pattern TEMPORARY_PATTERN_HOLDER = Pattern.compile('REGEXP>>(.*)<<')
private static final String JSON_VALUE_PATTERN_FOR_REGEX = 'REGEXP>>%s<<'
Body(Map<String, DslProperty> body) {
super(extractValue(body, {it.clientValue}), extractValue(body, {it.serverValue}))
super(extractValue(body, { DslProperty p -> p.clientValue}), extractValue(body, {DslProperty p -> p.serverValue}))
}
private static Map<String, Object> extractValue(Map<String, DslProperty> body, Closure valueProvider) {
@@ -26,8 +19,8 @@ class Body extends DslProperty {
} as Map<String, Object>
}
Body(List bodyAsList) {
super(bodyAsList.collect { it.clientValue }, bodyAsList.collect { it.serverValue })
Body(List<DslProperty> bodyAsList) {
super(bodyAsList.collect { DslProperty p -> p.clientValue }, bodyAsList.collect { DslProperty p -> p.serverValue })
}
Body(Object bodyAsValue) {
@@ -35,48 +28,16 @@ class Body extends DslProperty {
}
Body(GString bodyAsValue) {
super(extractValue(bodyAsValue, {it.clientValue}), extractValue(bodyAsValue, {it.serverValue}))
super(bodyAsValue, bodyAsValue)
}
Body(DslProperty bodyAsValue) {
super(bodyAsValue.clientValue, bodyAsValue.serverValue)
}
/**
* Due to the fact that we allow users to have a body with GString and different values inside
* we need to be prepared that they pass regexps around both on client and server side.
*
* In order to preserve the original JSON structure we need to convert the passed Regex patterns
* to a temporary string, then convert all to a legitimate JSON structure and then finally
* convert it back from string to a pattern.
*
* @param bodyAsValue - GString with passed values
* @param valueProvider - provider of values either for server or client side
* @return JSON structure with replaced client / server side parts
*/
private static Object extractValue(GString bodyAsValue, Closure valueProvider) {
GString gString = new GStringImpl(bodyAsValue.values.clone(), bodyAsValue.strings.clone())
Object[] values = bodyAsValue.values.collect { it instanceof DslProperty ? valueProvider(it) : it } as Object[]
Object[] valuesWithRegexpsAsTransformedStrings = values.collect {
it instanceof Pattern ? String.format(JSON_VALUE_PATTERN_FOR_REGEX, it.toString()) : it
} as Object[]
def parsedJson = new JsonSlurper().parseText(new GStringImpl(valuesWithRegexpsAsTransformedStrings, gString.strings))
return convertAllTemporaryRegexPlaceholdersBackToPatterns(parsedJson)
Body(MatchingStrategy matchingStrategy) {
super(matchingStrategy, matchingStrategy)
}
private static Object convertAllTemporaryRegexPlaceholdersBackToPatterns(parsedJson) {
JsonConverter.transformValues(parsedJson, { Object value ->
if (value instanceof String) {
String string = (String) value
Matcher matcher = TEMPORARY_PATTERN_HOLDER.matcher(string)
if (matcher.matches()) {
String pattern = matcher[0][1]
return Pattern.compile(pattern)
}
return value
}
return value
})
}
}

View File

@@ -9,26 +9,39 @@ import groovy.transform.ToString;
@CompileStatic
class MatchingStrategy extends DslProperty {
Type type
Type type
JSONCompareMode jsonCompareMode
MatchingStrategy(Object value, Type type) {
super(value)
this.type = type
}
MatchingStrategy(Object value, Type type) {
this(value, type, null)
}
MatchingStrategy(DslProperty value, Type type) {
super(value.clientValue, value.serverValue)
this.type = type
}
MatchingStrategy(Object value, Type type, JSONCompareMode jsonCompareMode) {
super(value)
this.type = type
this.jsonCompareMode = jsonCompareMode
}
enum Type {
EQUAL_TO("equalTo"), CONTAINS("contains"), MATCHING("matches"), NOT_MATCHING("doesNotMatch")
MatchingStrategy(DslProperty value, Type type) {
this(value, type, null)
}
final String name
MatchingStrategy(DslProperty value, Type type, JSONCompareMode jsonCompareMode) {
super(value.clientValue, value.serverValue)
this.type = type
this.jsonCompareMode = jsonCompareMode
}
Type(name) {
this.name = name
}
}
enum Type {
EQUAL_TO("equalTo"), CONTAINS("contains"), MATCHING("matches"), NOT_MATCHING("doesNotMatch"),
EQUAL_TO_JSON("equalToJson"), EQUAL_TO_XML("equalToXml")
final String name
Type(name) {
this.name = name
}
}
}

View File

@@ -20,20 +20,4 @@ class QueryParameters {
parameters << new QueryParameter(parameterName, parameterValue)
}
MatchingStrategy equalTo(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO)
}
MatchingStrategy containing(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.CONTAINS)
}
MatchingStrategy matching(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.MATCHING)
}
MatchingStrategy notMatching(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.NOT_MATCHING)
}
}

View File

@@ -3,6 +3,7 @@ import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import groovy.xml.MarkupBuilder
@TypeChecked
@EqualsAndHashCode
@@ -88,6 +89,10 @@ class Request extends Common {
this.body = new Body(convertObjectsToDslProperties(body))
}
void body(DslProperty dslProperty) {
this.body = new Body(dslProperty)
}
void body(Object bodyAsValue) {
this.body = new Body(bodyAsValue)
}
@@ -95,6 +100,51 @@ class Request extends Common {
Body getBody() {
return body
}
MatchingStrategy equalTo(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO)
}
MatchingStrategy containing(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.CONTAINS)
}
MatchingStrategy matching(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.MATCHING)
}
MatchingStrategy notMatching(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.NOT_MATCHING)
}
MatchingStrategy equalToXml(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_XML)
}
MatchingStrategy equalToJson(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_JSON)
}
MatchingStrategy equalToJson(Object value, JSONCompareMode jsonCompareMode) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_JSON)
}
MatchingStrategy equalToJsonStrictly(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_JSON, JSONCompareMode.STRICT)
}
MatchingStrategy equalToJsonLeniently(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_JSON, JSONCompareMode.LENIENT)
}
MatchingStrategy equalToJsonNonExtensibly(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_JSON, JSONCompareMode.NON_EXTENSIBLE)
}
MatchingStrategy equalToJsonWithStrictOrder(Object value) {
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_JSON, JSONCompareMode.STRICT_ORDER)
}
}
@CompileStatic

View File

@@ -0,0 +1,5 @@
package io.codearte.accurest.util
enum ContentType {
JSON, XML, UNKNOWN
}

View File

@@ -0,0 +1,192 @@
package io.codearte.accurest.util
import groovy.json.JsonException
import groovy.json.JsonSlurper
import groovy.transform.TypeChecked
import io.codearte.accurest.dsl.internal.DslProperty
import io.codearte.accurest.dsl.internal.Headers
import io.codearte.accurest.dsl.internal.MatchingStrategy
import org.codehaus.groovy.runtime.GStringImpl
import java.util.regex.Matcher
import java.util.regex.Pattern
import static org.apache.commons.lang3.StringEscapeUtils.escapeJson
import static org.apache.commons.lang3.StringEscapeUtils.escapeXml11
@TypeChecked
class ContentUtils {
private static final Pattern TEMPORARY_PATTERN_HOLDER = Pattern.compile('REGEXP>>(.*)<<')
private static final String JSON_VALUE_PATTERN_FOR_REGEX = 'REGEXP>>%s<<'
/**
* Due to the fact that we allow users to have a body with GString and different values inside
* we need to be prepared that they pass regexps around both on client and server side.
*
* In order to preserve the original JSON structure we need to convert the passed Regex patterns
* to a temporary string, then convert all to a legitimate JSON structure and then finally
* convert it back from string to a pattern.
*
* @param bodyAsValue - GString with passed values
* @param valueProvider - provider of values either for server or client side
* @return JSON structure with replaced client / server side parts
*/
public static Object extractValue(GString bodyAsValue, ContentType contentType, Closure valueProvider) {
if (contentType == ContentType.JSON) {
return extractValueForJSON(bodyAsValue, valueProvider)
}
if (contentType == ContentType.XML) {
return extractValueForXML(bodyAsValue, valueProvider)
}
// else Brute force :(
try {
return extractValueForJSON(bodyAsValue, valueProvider)
} catch(JsonException e) {
// Not a JSON format
return extractValueForXML(bodyAsValue, valueProvider)
}
return bodyAsValue
}
public static Object extractValue(GString bodyAsValue, Closure valueProvider) {
return extractValue(bodyAsValue, ContentType.UNKNOWN, valueProvider)
}
private static Object extractValueForJSON(GString bodyAsValue, Closure valueProvider) {
GString transformedString = new GStringImpl(
bodyAsValue.values.collect { transformJSONStringValue(it, valueProvider) } as String[],
bodyAsValue.strings.clone() as String[]
)
def parsedJson = new JsonSlurper().parseText(transformedString.toString())
return convertAllTemporaryRegexPlaceholdersBackToPatterns(parsedJson)
}
private static GStringImpl extractValueForXML(GString bodyAsValue, Closure valueProvider) {
return new GStringImpl(
bodyAsValue.values.collect { transformXMLStringValue(it, valueProvider) } as String[],
bodyAsValue.strings.clone() as String[]
)
}
private static String transformJSONStringValue(Object obj, Closure valueProvider) {
return obj.toString()
}
private static String transformJSONStringValue(DslProperty dslProperty, Closure valueProvider) {
return transformJSONStringValue(valueProvider(dslProperty), valueProvider)
}
private static String transformJSONStringValue(Pattern pattern, Closure valueProvider) {
return String.format(JSON_VALUE_PATTERN_FOR_REGEX, pattern.pattern())
}
private static String transformXMLStringValue(Object obj, Closure valueProvider) {
return escapeXml11(obj.toString())
}
private static String transformXMLStringValue(DslProperty dslProperty, Closure valueProvider) {
return transformXMLStringValue(valueProvider(dslProperty), valueProvider)
}
private static Object convertAllTemporaryRegexPlaceholdersBackToPatterns(parsedJson) {
JsonConverter.transformValues(parsedJson, { Object value ->
if (value instanceof String) {
String string = (String) value
Matcher matcher = TEMPORARY_PATTERN_HOLDER.matcher(string)
if (matcher.matches()) {
List val = matcher[0] as List
String pattern = val[1]
return Pattern.compile(pattern)
}
return value
}
return value
})
}
public static ContentType recognizeContentTypeFromHeader(Headers headers) {
String content = headers?.entries.find { it.name == "Content-Type" } ?.clientValue?.toString()
if (content?.endsWith("json")) {
return ContentType.JSON
}
if (content?.endsWith("xml")) {
return ContentType.XML
}
return ContentType.UNKNOWN
}
public static MatchingStrategy.Type getEqualsTypeFromContentType(ContentType contentType) {
switch (contentType) {
case ContentType.JSON:
return MatchingStrategy.Type.EQUAL_TO_JSON
case ContentType.XML:
return MatchingStrategy.Type.EQUAL_TO_XML
}
return MatchingStrategy.Type.EQUAL_TO
}
public static ContentType recognizeContentTypeFromContent(GString gstring) {
if (isJsonType(gstring)) {
return ContentType.JSON
}
if (isXmlType(gstring)) {
return ContentType.XML
}
return ContentType.UNKNOWN
}
public static ContentType recognizeContentTypeFromContent(Map jsonMap) {
return ContentType.JSON
}
public static ContentType recognizeContentTypeFromContent(List jsonList) {
return ContentType.JSON
}
public static ContentType recognizeContentTypeFromContent(Object gstring) {
return ContentType.UNKNOWN
}
public static boolean isJsonType(GString gstring) {
GString stringWithoutValues = new GStringImpl(
gstring.values.collect({
it instanceof String || it instanceof GString ? it.toString() : escapeJson(it.toString())
}) as Object[],
gstring.strings.clone() as String[]
)
try {
new JsonSlurper().parseText(stringWithoutValues.toString())
return true
} catch (JsonException e) {
// Not JSON
}
return false
}
public static boolean isXmlType(GString gstring) {
GString stringWithoutValues = new GStringImpl(
gstring.values.collect({
it instanceof String || it instanceof GString ? it.toString() : escapeXml11(it.toString())
}) as Object[],
gstring.strings.clone() as String[]
)
try {
new XmlSlurper().parseText(stringWithoutValues.toString())
return true
} catch (Exception e) {
// Not XML
}
return false
}
public static ContentType recognizeContentTypeFromMatchingStrategy(MatchingStrategy.Type type) {
switch (type) {
case MatchingStrategy.Type.EQUAL_TO_XML:
return ContentType.XML
case MatchingStrategy.Type.EQUAL_TO_JSON:
return ContentType.JSON
}
return ContentType.UNKNOWN
}
}

View File

@@ -181,7 +181,7 @@ class WiremockGroovyDslSpec extends WiremockSpec {
"urlPattern": "/[0-9]{2}",
"bodyPatterns": [
{
"equalTo":"{\\"name\\":\\"Jan\\"}"
"equalToJson":"{\\"name\\":\\"Jan\\"}"
}
]
},
@@ -198,6 +198,314 @@ class WiremockGroovyDslSpec extends WiremockSpec {
stubMappingIsValidWiremockStub(wiremockStub)
}
def 'should use equalToJson when body match is defined as map'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method('GET')
url $(client(~/\/[0-9]{2}/), server('/12'))
body(
id: value(
client('123'),
server({ regex('[0-9]+') })
),
surname: $(
client('Kowalsky'),
server('Lewandowski')
),
name: 'Jan',
created: $(client('2014-02-02 12:23:43'), server({ currentDate(it) }))
)
}
response {
status 200
}
}
when:
String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockClientStub()
then:
new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText('''
{
"request": {
"method": "GET",
"urlPattern": "/[0-9]{2}",
"bodyPatterns": [
{
"equalToJson": "{\\"id\\":\\"123\\",\\"surname\\":\\"Kowalsky\\",\\"name\\":\\"Jan\\",\\"created\\":\\"2014-02-02 12:23:43\\"}"
}
]
},
"response": {
"status": 200,
}
}
''')
and:
stubMappingIsValidWiremockStub(wiremockStub)
}
def 'should use equalToJson when content type ends with json'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url "/users"
headers {
header "Content-Type", "customtype/json"
}
body """
{
"name": "Jan"
}
"""
}
response {
status 200
}
}
when:
String json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"url": "/users",
"headers": {
"Content-Type": {
"equalTo": "customtype/json"
}
},
"bodyPatterns": [
{
"equalToJson":"{\\"name\\":\\"Jan\\"}"
}
]
},
"response": {
"status": 200
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
def 'should use equalToXml when content type ends with xml'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url "/users"
headers {
header "Content-Type", "customtype/xml"
}
body """<name>${value(client('Jozo'), server('Denis'))}</name><jobId>${value(client("<test>"), server('1234567890'))}</jobId>"""
}
response {
status 200
}
}
when:
String json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"url": "/users",
"headers": {
"Content-Type": {
"equalTo": "customtype/xml"
}
},
"bodyPatterns": [
{
"equalToXml":"<name>Jozo</name><jobId>&lt;test&gt;</jobId>"
}
]
},
"response": {
"status": 200
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
def 'should use equalToXml when content type is parsable xml'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url "/users"
body """<user><name>${value(client('Jozo'), server('Denis'))}</name><jobId>${value(client("<test>"), server('1234567890'))}</jobId></user>"""
}
response {
status 200
}
}
when:
String json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"url": "/users",
"bodyPatterns": [
{
"equalToXml":"<user><name>Jozo</name><jobId>&lt;test&gt;</jobId></user>"
}
]
},
"response": {
"status": 200
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
def 'should support xml as a response body'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url "/users"
}
response {
status 200
body """<user><name>${value(client('Jozo'), server('Denis'))}</name><jobId>${value(client("<test>"), server('1234567890'))}</jobId></user>"""
}
}
when:
String json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"url": "/users"
},
"response": {
"status": 200,
"body":"<user><name>Jozo</name><jobId>&lt;test&gt;</jobId></user>"
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
def 'should use equalToJson compare mode'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url "/users"
body equalToJsonWithStrictOrder('''{"name":"Jan"}''')
}
response {
status 200
}
}
when:
String json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"url": "/users",
"bodyPatterns": [
{
"equalToJson":"{\\"name\\":\\"Jan\\"}",
"jsonCompareMode":"STRICT_ORDER"
}
]
},
"response": {
"status": 200
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
def 'should use equalToJson'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url "/users"
body equalToJson('''{"name":"Jan"}''')
}
response {
status 200
}
}
when:
String json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"url": "/users",
"bodyPatterns": [
{
"equalToJson":"{\\"name\\":\\"Jan\\"}"
}
]
},
"response": {
"status": 200
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
def 'should use equalToXml'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url "/users"
body equalToXml("""<name>${value(client('Jozo'), server('Denis'))}</name><jobId>${value(client("<test>"), server('1234567890'))}</jobId>""")
}
response {
status 200
}
}
when:
String json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"url": "/users",
"bodyPatterns": [
{
"equalToXml":"<name>Jozo</name><jobId>&lt;test&gt;</jobId>"
}
]
},
"response": {
"status": 200
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
def 'should convert groovy dsl stub with regexp Body as String to wiremock stub for the client side'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
@@ -232,9 +540,7 @@ class WiremockGroovyDslSpec extends WiremockSpec {
"method": "GET",
"urlPattern": "/[0-9]{2}",
"bodyPatterns": [
{
"matches":"\\\\{\\"personalId\\":\\"^[0-9]{11}$\\"\\\\}"
}
{"matches": ".*personalId\\":.?\\"?^[0-9]{11}$\\"?.*"}
]
},
"response": {
@@ -294,9 +600,8 @@ class WiremockGroovyDslSpec extends WiremockSpec {
},
"url": "/fraudcheck",
"bodyPatterns": [
{
"matches": "\\\\{\\"clientPesel\\":\\"[0-9]{10}\\",\\"loanAmount\\":123.123\\\\}"
}
{"matches": ".*clientPesel\\":.?\\"?[0-9]{10}\\"?.*"},
{"matches": ".*loanAmount\\":.?\\"?123.123\\"?.*"}
]
},
"response": {
@@ -427,78 +732,78 @@ class WiremockGroovyDslSpec extends WiremockSpec {
def "should generate request with urlPath for client side"() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
urlPath $(client("boxes"), server("items"))
}
response {
status 200
}
}
when:
def json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"urlPath": "boxes"
},
"response": {
"status": 200,
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
urlPath $(client("boxes"), server("items"))
}
response {
status 200
}
}
''')
when:
def json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"urlPath": "boxes"
},
"response": {
"status": 200,
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
stubMappingIsValidWiremockStub(json)
}
def "should generate simple request with urlPath for client side"() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
urlPath "boxes"
}
response {
status 200
}
}
when:
def json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"urlPath": "boxes"
},
"response": {
"status": 200,
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
urlPath "boxes"
}
response {
status 200
}
}
''')
when:
def json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"urlPath": "boxes"
},
"response": {
"status": 200,
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
stubMappingIsValidWiremockStub(json)
}
def "should not allow regexp in url for server value"() {
when:
GroovyDsl.make {
request {
method 'GET'
url(regex(/users\/[0-9]*/)) {
queryParameters {
parameter 'age': notMatching("^\\w*\$")
parameter 'name': matching("Denis.*")
GroovyDsl.make {
request {
method 'GET'
url(regex(/users\/[0-9]*/)) {
queryParameters {
parameter 'age': notMatching("^\\w*\$")
parameter 'name': matching("Denis.*")
}
}
}
response {
status 200
}
}
response {
status 200
}
}
then:
def e = thrown(IllegalStateException)
e.message.contains "Url can't be a pattern for the server side"
@@ -547,44 +852,44 @@ class WiremockGroovyDslSpec extends WiremockSpec {
def "should generate request with url and queryParameters for client side"() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url($(client(regex(/users\/[0-9]*/)), server("users/123"))) {
queryParameters {
parameter 'age': $(client(notMatching("^\\w*\$")), server(10))
parameter 'name': $(client(matching("Denis.*")), server("Denis"))
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
url($(client(regex(/users\/[0-9]*/)), server("users/123"))) {
queryParameters {
parameter 'age': $(client(notMatching("^\\w*\$")), server(10))
parameter 'name': $(client(matching("Denis.*")), server("Denis"))
}
}
}
response {
status 200
}
}
response {
status 200
}
}
when:
def json = toWiremockClientJsonStub(groovyDsl)
def json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"urlPattern": "users/[0-9]*",
"queryParameters": {
"age": {
"doesNotMatch": "^\\\\w*$"
},
"name": {
"matches": "Denis.*"
}
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"urlPattern": "users/[0-9]*",
"queryParameters": {
"age": {
"doesNotMatch": "^\\\\w*$"
},
"name": {
"matches": "Denis.*"
}
}
},
"response": {
"status": 200,
}
},
"response": {
"status": 200,
}
}
''')
''')
and:
stubMappingIsValidWiremockStub(json)
stubMappingIsValidWiremockStub(json)
}
def "should generate stub with some headers section for client side"() {
@@ -622,6 +927,74 @@ class WiremockGroovyDslSpec extends WiremockSpec {
''')
}
def 'should convert groovy dsl stub with rich tree Body as String to wiremock stub for the client side'() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method('GET')
url $(client(~/\/[0-9]{2}/), server('/12'))
body """\
{
"personalId": "${value(client(regex('[0-9]{11}')), server('57593728525'))}",
"firstName": "${value(client(regex('.*')), server('Bruce'))}",
"lastName": "${value(client(regex('.*')), server('Lee'))}",
"birthDate": "${value(client(regex('[0-9]{4}-[0-9]{2}-[0-9]{2}')), server('1985-12-12'))}",
"errors": [
{
"propertyName": "${value(client(regex('[0-9]{2}')), server('04'))}",
"providerValue": "Test"
},
{
"propertyName": "${value(client(regex('[0-9]{2}')), server('08'))}",
"providerValue": "Test"
}
]
}
"""
}
response {
status 200
body("""\
{
"name": "Jan"
}
"""
)
headers {
header 'Content-Type': 'text/plain'
}
}
}
when:
String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockClientStub()
then:
new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText('''
{
"request": {
"method": "GET",
"urlPattern": "/[0-9]{2}",
"bodyPatterns": [
{"matches": ".*birthDate\\":.?\\"?[0-9]{4}-[0-9]{2}-[0-9]{2}\\"?.*"},
{"matches": ".*propertyName\\":.?\\"?[0-9]{2}\\"?.*"},
{"matches": ".*providerValue\\":.?\\"?Test\\"?.*"},
{"matches": ".*propertyName\\":.?\\"?[0-9]{2}\\"?.*"},
{"matches": ".*providerValue\\":.?\\"?Test\\"?.*"},
{"matches": ".*firstName\\":.?\\"?.*\\"?.*"},
{"matches": ".*lastName\\":.?\\"?.*\\"?.*"},
{"matches": ".*personalId\\":.?\\"?[0-9]{11}\\"?.*"}
]
},
"response": {
"status": 200,
"body": "{\\"name\\":\\"Jan\\"}",
"headers": {
"Content-Type": "text/plain"
}
}
}
''')
}
String toJsonString(value) {
new JsonBuilder(value).toPrettyString()
}

View File

@@ -5,7 +5,7 @@ import spock.lang.Specification
import java.util.regex.Pattern
class WiremockSpec extends Specification {
abstract class WiremockSpec extends Specification {
void stubMappingIsValidWiremockStub(String mappingDefinition) {
StubMapping stubMapping = StubMapping.buildFrom(mappingDefinition)

View File

@@ -52,7 +52,7 @@ class BasicFunctionalSpec extends IntegrationSpec {
},
"url": "/api/12",
"bodyPatterns": [
{ "equalTo": "[{\\"text\\":\\"Gonna see you at Warsaw\\"}]" }
{ "equalToJson": "[{\\"text\\":\\"Gonna see you at Warsaw\\"}]" }
]
},
"response": {