Added support for form param
without this change we supported only text and json content type body. with this change we also support url encoded form parameters fixes gh-578
This commit is contained in:
@@ -43,6 +43,7 @@ import org.springframework.cloud.contract.verifier.config.ContractVerifierConfig
|
||||
import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor
|
||||
import org.springframework.cloud.contract.verifier.template.TemplateProcessor
|
||||
import org.springframework.cloud.contract.verifier.util.ContentType
|
||||
import org.springframework.cloud.contract.verifier.util.ContentUtils
|
||||
import org.springframework.cloud.contract.verifier.util.JsonPaths
|
||||
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
|
||||
import org.springframework.cloud.contract.verifier.util.MapConverter
|
||||
@@ -314,7 +315,7 @@ abstract class MethodBodyBuilder {
|
||||
if (convertedResponseBody instanceof GString) {
|
||||
convertedResponseBody = extractValue(convertedResponseBody as GString, contentType, { Object o -> o instanceof DslProperty ? o.serverValue : o })
|
||||
}
|
||||
if (contentType != ContentType.TEXT) {
|
||||
if (contentType != ContentType.TEXT && contentType != ContentType.FORM) {
|
||||
convertedResponseBody = MapConverter.getTestSideValues(convertedResponseBody)
|
||||
} else {
|
||||
convertedResponseBody = StringEscapeUtils.escapeJava(convertedResponseBody.toString())
|
||||
@@ -632,13 +633,13 @@ abstract class MethodBodyBuilder {
|
||||
*/
|
||||
protected Object extractServerValueFromBody(bodyValue) {
|
||||
if (bodyValue instanceof GString) {
|
||||
bodyValue = extractValue(bodyValue, ContentType.from(MapConverter.getTestSideValues(this.contract.request?.headers?.entries?.find {
|
||||
it.name.toLowerCase() == "Content-Type".toLowerCase()
|
||||
}).toString()), GET_SERVER_VALUE)
|
||||
} else {
|
||||
bodyValue = MapConverter.transformValues(bodyValue, GET_SERVER_VALUE)
|
||||
return extractValue(bodyValue, contentType(), GET_SERVER_VALUE)
|
||||
}
|
||||
return bodyValue
|
||||
return MapConverter.transformValues(bodyValue, GET_SERVER_VALUE)
|
||||
}
|
||||
|
||||
protected ContentType contentType() {
|
||||
return ContentUtils.recognizeContentTypeFromTestHeader(this.contract.request?.headers)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -200,9 +200,23 @@ abstract class RequestProcessingMethodBodyBuilder extends MethodBodyBuilder {
|
||||
@Override
|
||||
protected String getBodyAsString() {
|
||||
Object bodyValue = extractServerValueFromBody(request.body.serverValue)
|
||||
String json = new JsonOutput().toJson(bodyValue)
|
||||
json = convertUnicodeEscapesIfRequired(json)
|
||||
return trimRepeatedQuotes(json)
|
||||
if (contentType() == ContentType.FORM) {
|
||||
if (bodyValue instanceof Map) {
|
||||
// [a:3, b:4] == "a=3&b=4"
|
||||
return ((Map) bodyValue).collect {
|
||||
convertUnicodeEscapesIfRequired(it.key.toString() + "=" + it.value)
|
||||
}.join("&")
|
||||
} else if (bodyValue instanceof List) {
|
||||
// ["a=3", "b=4"] == "a=3&b=4"
|
||||
return ((List) bodyValue).collect {
|
||||
convertUnicodeEscapesIfRequired(it.toString())
|
||||
}.join("&")
|
||||
}
|
||||
} else {
|
||||
String json = new JsonOutput().toJson(bodyValue)
|
||||
json = convertUnicodeEscapesIfRequired(json)
|
||||
return trimRepeatedQuotes(json)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder
|
||||
import com.github.tomakehurst.wiremock.matching.StringValuePattern
|
||||
import com.github.tomakehurst.wiremock.matching.UrlPattern
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.json.StringEscapeUtils
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TypeChecked
|
||||
import groovy.transform.TypeCheckingMode
|
||||
@@ -71,9 +72,10 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
return null
|
||||
}
|
||||
RequestPatternBuilder requestPatternBuilder = appendMethodAndUrl()
|
||||
appendHeaders(requestPatternBuilder)
|
||||
appendQueryParameters(requestPatternBuilder)
|
||||
appendBody(requestPatternBuilder)
|
||||
ContentType contentType = tryToGetContentType(request?.body?.clientValue, request?.headers)
|
||||
appendHeaders(requestPatternBuilder, contentType)
|
||||
appendQueryParameters(requestPatternBuilder, contentType)
|
||||
appendBody(requestPatternBuilder, contentType)
|
||||
appendMultipart(requestPatternBuilder)
|
||||
return requestPatternBuilder.build()
|
||||
}
|
||||
@@ -87,11 +89,10 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
return RequestPatternBuilder.newRequestPattern(requestMethod, urlPattern)
|
||||
}
|
||||
|
||||
private void appendBody(RequestPatternBuilder requestPattern) {
|
||||
private void appendBody(RequestPatternBuilder requestPattern, ContentType contentType) {
|
||||
if (!request.body) {
|
||||
return
|
||||
}
|
||||
ContentType contentType = tryToGetContentType(request.body.clientValue, request.headers)
|
||||
if (contentType == ContentType.JSON) {
|
||||
def originalBody = getMatchingStrategyFromBody(request.body)?.clientValue
|
||||
def body = JsonToJsonPathsConverter.removeMatchingJsonPaths(originalBody, request.matchers)
|
||||
@@ -113,9 +114,10 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
requestPattern.withRequestBody(WireMock.equalToXml(getMatchingStrategy(request.body.clientValue).clientValue.toString()))
|
||||
} else if (containsPattern(request?.body)) {
|
||||
MatchingStrategy matchingStrategy = appendBodyRegexpMatchPattern(request.body)
|
||||
requestPattern.withRequestBody(convertToValuePattern(matchingStrategy))
|
||||
requestPattern.withRequestBody(convertToValuePattern(matchingStrategy, contentType))
|
||||
} else {
|
||||
requestPattern.withRequestBody(convertToValuePattern(getMatchingStrategy(request.body.clientValue)))
|
||||
requestPattern.withRequestBody(convertToValuePattern(
|
||||
getMatchingStrategy(request.body.clientValue), contentType))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,12 +141,12 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
private void appendHeaders(RequestPatternBuilder requestPattern) {
|
||||
private void appendHeaders(RequestPatternBuilder requestPattern, ContentType contentType) {
|
||||
if(!request.headers) {
|
||||
return
|
||||
}
|
||||
request.headers.entries.each {
|
||||
requestPattern.withHeader(it.name, convertToValuePattern(it.clientValue))
|
||||
requestPattern.withHeader(it.name, convertToValuePattern(it.clientValue, contentType))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,15 +192,15 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
return clientSide
|
||||
}
|
||||
|
||||
private void appendQueryParameters(RequestPatternBuilder requestPattern) {
|
||||
private void appendQueryParameters(RequestPatternBuilder requestPattern, ContentType contentType) {
|
||||
QueryParameters queryParameters = request?.urlPath?.queryParameters ?: request?.url?.queryParameters
|
||||
queryParameters?.parameters?.each {
|
||||
requestPattern.withQueryParam(it.name, convertToValuePattern(it.clientValue))
|
||||
requestPattern.withQueryParam(it.name, convertToValuePattern(it.clientValue, contentType))
|
||||
}
|
||||
}
|
||||
|
||||
@TypeChecked(TypeCheckingMode.SKIP)
|
||||
private static StringValuePattern convertToValuePattern(Object object) {
|
||||
private static StringValuePattern convertToValuePattern(Object object, ContentType contentType) {
|
||||
switch (object) {
|
||||
case Pattern:
|
||||
Pattern value = object as Pattern
|
||||
@@ -215,7 +217,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
return WireMock.absent()
|
||||
default:
|
||||
try {
|
||||
return WireMock."${value.type.name}"(value.clientValue)
|
||||
return WireMock."${value.type.name}"(clientBody(value.clientValue, contentType))
|
||||
} catch (Throwable t) {
|
||||
log.error("Exception occurred while trying to call WireMock.${value.type.name}(${value.clientValue})", t)
|
||||
throw t
|
||||
@@ -226,6 +228,23 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
protected static Object clientBody(Object bodyValue, ContentType contentType) {
|
||||
if (contentType == ContentType.FORM) {
|
||||
if (bodyValue instanceof Map) {
|
||||
// [a:3, b:4] == "a=3&b=4"
|
||||
return ((Map) bodyValue).collect {
|
||||
StringEscapeUtils.unescapeJavaScript(it.key.toString() + "=" + it.value)
|
||||
}.join("&")
|
||||
} else if (bodyValue instanceof List) {
|
||||
// ["a=3", "b=4"] == "a=3&b=4"
|
||||
return ((List) bodyValue).collect {
|
||||
StringEscapeUtils.unescapeJavaScript(it.toString())
|
||||
}.join("&")
|
||||
}
|
||||
}
|
||||
return bodyValue
|
||||
}
|
||||
|
||||
private MatchingStrategy getMatchingStrategyFromBody(Body body) {
|
||||
if(!body) {
|
||||
return null
|
||||
|
||||
@@ -26,6 +26,7 @@ enum ContentType {
|
||||
JSON("application/json"),
|
||||
XML("application/xml"),
|
||||
TEXT("text/plain"),
|
||||
FORM("application/x-www-form-urlencoded"),
|
||||
UNKNOWN("application/octet-stream")
|
||||
|
||||
final String mimeType
|
||||
@@ -34,19 +35,4 @@ enum ContentType {
|
||||
this.mimeType = mimeType
|
||||
}
|
||||
|
||||
static ContentType from(String header) {
|
||||
try {
|
||||
if (header.contains("json")) {
|
||||
return JSON
|
||||
} else if (header.contains("xml")) {
|
||||
return XML
|
||||
} else if (header.contains("text") ||
|
||||
header.contains("application/x-www-form-urlencoded")) {
|
||||
// we want both to be treated as text
|
||||
return TEXT
|
||||
}
|
||||
} catch(e) {}
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import groovy.util.logging.Slf4j
|
||||
import org.codehaus.groovy.runtime.GStringImpl
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
|
||||
import org.springframework.cloud.contract.spec.internal.Header
|
||||
import org.springframework.cloud.contract.spec.internal.Headers
|
||||
import org.springframework.cloud.contract.spec.internal.MatchingStrategy
|
||||
import org.springframework.cloud.contract.spec.internal.NamedProperty
|
||||
@@ -76,7 +77,7 @@ class ContentUtils {
|
||||
if (bodyAsValue.isEmpty()){
|
||||
return bodyAsValue
|
||||
}
|
||||
if (contentType == ContentType.TEXT) {
|
||||
if (contentType == ContentType.TEXT || contentType == ContentType.FORM) {
|
||||
return extractValueForText(bodyAsValue, valueProvider)
|
||||
}
|
||||
if (contentType == ContentType.JSON) {
|
||||
@@ -281,8 +282,9 @@ class ContentUtils {
|
||||
return val[1]
|
||||
}
|
||||
|
||||
static ContentType recognizeContentTypeFromHeader(Headers headers) {
|
||||
String content = headers?.entries.find { it.name == "Content-Type" } ?.clientValue?.toString()
|
||||
static ContentType recognizeContentTypeFromHeader(Headers headers, Closure<Object> closure) {
|
||||
Header header = headers?.entries?.find { it.name == "Content-Type" }
|
||||
String content = closure(header)?.toString()
|
||||
if (content?.contains("json")) {
|
||||
return ContentType.JSON
|
||||
}
|
||||
@@ -292,9 +294,20 @@ class ContentUtils {
|
||||
if (content?.contains("text")) {
|
||||
return ContentType.TEXT
|
||||
}
|
||||
if (content?.contains("form-urlencoded")) {
|
||||
return ContentType.FORM
|
||||
}
|
||||
return ContentType.UNKNOWN
|
||||
}
|
||||
|
||||
static ContentType recognizeContentTypeFromHeader(Headers headers) {
|
||||
return recognizeContentTypeFromHeader(headers, { Header header -> header?.clientValue })
|
||||
}
|
||||
|
||||
static ContentType recognizeContentTypeFromTestHeader(Headers headers) {
|
||||
return recognizeContentTypeFromHeader(headers, { Header header -> header?.serverValue })
|
||||
}
|
||||
|
||||
static MatchingStrategy.Type getEqualsTypeFromContentType(ContentType contentType) {
|
||||
switch (contentType) {
|
||||
case ContentType.JSON:
|
||||
|
||||
@@ -528,6 +528,61 @@ DocumentContext parsedJson = JsonPath.parse(json);
|
||||
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) }
|
||||
}
|
||||
|
||||
@Issue("#578")
|
||||
def "should work for form parameters [#methodBuilderName]"() {
|
||||
given:
|
||||
Contract contractDsl = Contract.make {
|
||||
request {
|
||||
method POST()
|
||||
urlPath('/oauth/token')
|
||||
headers {
|
||||
header(authorization(), anyNonBlankString())
|
||||
header(contentType(), applicationFormUrlencoded())
|
||||
header(accept(), applicationJson())
|
||||
}
|
||||
body([
|
||||
username : 'user',
|
||||
password : 'password',
|
||||
grant_type: 'password'
|
||||
])
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
headers {
|
||||
header(contentType(), applicationJson())
|
||||
}
|
||||
body([
|
||||
refresh_token: 'RANDOM_REFRESH_TOKEN',
|
||||
access_token : 'RANDOM_ACCESS_TOKEN',
|
||||
token_type : 'bearer',
|
||||
expires_in : 3600,
|
||||
scope : ['task'],
|
||||
user : [
|
||||
id : 1,
|
||||
username: 'user',
|
||||
name : 'User'
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
MethodBodyBuilder builder = methodBuilder(contractDsl)
|
||||
BlockBuilder blockBuilder = new BlockBuilder(" ")
|
||||
when:
|
||||
builder.appendTo(blockBuilder)
|
||||
then:
|
||||
String test = blockBuilder.toString()
|
||||
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test)
|
||||
test.contains("username=user&password=password&grant_type=password")
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(contractDsl)
|
||||
where:
|
||||
methodBuilderName | methodBuilder
|
||||
"MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
|
||||
"MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) }
|
||||
"JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
|
||||
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) }
|
||||
}
|
||||
|
||||
@Issue("#509")
|
||||
def "classToCheck() should return class of object"() {
|
||||
given:
|
||||
|
||||
@@ -1834,6 +1834,80 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
|
||||
server?.shutdown()
|
||||
}
|
||||
|
||||
@Issue('#578')
|
||||
def "should generate a stub for a request with form parameters"() {
|
||||
given:
|
||||
Contract groovyDsl = Contract.make {
|
||||
request {
|
||||
method POST()
|
||||
urlPath('/oauth/token')
|
||||
headers {
|
||||
header(authorization(), anyNonBlankString())
|
||||
header(contentType(), applicationFormUrlencoded())
|
||||
header(accept(), applicationJson())
|
||||
}
|
||||
body([
|
||||
username : 'user',
|
||||
password : 'password',
|
||||
grant_type: 'password'
|
||||
])
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
headers {
|
||||
header(contentType(), applicationJson())
|
||||
}
|
||||
body([
|
||||
refresh_token: 'RANDOM_REFRESH_TOKEN',
|
||||
access_token : 'RANDOM_ACCESS_TOKEN',
|
||||
token_type : 'bearer',
|
||||
expires_in : 3600,
|
||||
scope : ['task'],
|
||||
user : [
|
||||
id : 1,
|
||||
username: 'user',
|
||||
name : 'User'
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
when:
|
||||
def json = toWireMockClientJsonStub(groovyDsl)
|
||||
then:
|
||||
AssertionUtil.assertThatJsonsAreEqual(('''
|
||||
{
|
||||
"request" : {
|
||||
"urlPath" : "/oauth/token",
|
||||
"method" : "POST",
|
||||
"headers" : {
|
||||
"Authorization" : {
|
||||
"matches" : "^\\\\s*\\\\S[\\\\S\\\\s]*"
|
||||
},
|
||||
"Content-Type" : {
|
||||
"equalTo" : "application/x-www-form-urlencoded"
|
||||
},
|
||||
"Accept" : {
|
||||
"equalTo" : "application/json"
|
||||
}
|
||||
},
|
||||
"bodyPatterns" : [ {
|
||||
"equalTo" : "username=user&password=password&grant_type=password"
|
||||
} ]
|
||||
},
|
||||
"response" : {
|
||||
"status" : 200,
|
||||
"body" : "{\\"access_token\\":\\"RANDOM_ACCESS_TOKEN\\",\\"refresh_token\\":\\"RANDOM_REFRESH_TOKEN\\",\\"scope\\":[\\"task\\"],\\"token_type\\":\\"bearer\\",\\"expires_in\\":3600,\\"user\\":{\\"name\\":\\"User\\",\\"id\\":1,\\"username\\":\\"user\\"}}",
|
||||
"headers" : {
|
||||
"Content-Type" : "application/json"
|
||||
},
|
||||
"transformers" : [ "response-template", "foo-transformer" ]
|
||||
}
|
||||
}
|
||||
'''), json)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(json)
|
||||
}
|
||||
|
||||
@Issue('#269')
|
||||
def "should create a stub for dot separated keys"() {
|
||||
given:
|
||||
|
||||
Reference in New Issue
Block a user