Merge pull request #181 from RemboL/tech/add-multipart-named-parameter
[#180] add multipart support
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
package io.codearte.accurest.builder
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TypeChecked
|
||||
import groovy.transform.TypeCheckingMode
|
||||
@@ -11,6 +10,8 @@ import io.codearte.accurest.dsl.internal.Request
|
||||
import io.codearte.accurest.dsl.internal.Url
|
||||
import io.codearte.accurest.util.MapConverter
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@PackageScope
|
||||
@TypeChecked
|
||||
class MockMvcSpockMethodBodyBuilder extends SpockMethodBodyBuilder {
|
||||
@@ -28,6 +29,9 @@ class MockMvcSpockMethodBodyBuilder extends SpockMethodBodyBuilder {
|
||||
if (request.body) {
|
||||
bb.addLine(".body('''$bodyAsString''')")
|
||||
}
|
||||
if (request.multipart) {
|
||||
multipartParameters?.each { Map.Entry<String, Object> entry -> bb.addLine(getMultipartParameterLine(entry)) }
|
||||
}
|
||||
bb.unindent()
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,17 @@ abstract class SpockMethodBodyBuilder {
|
||||
return trimRepeatedQuotes(json)
|
||||
}
|
||||
|
||||
protected Map<String, Object> getMultipartParameters() {
|
||||
return (Map<String, Object>)request?.multipart?.serverValue
|
||||
}
|
||||
|
||||
protected String getMultipartParameterLine(Map.Entry<String, Object> parameter) {
|
||||
if (parameter.value instanceof NamedProperty) {
|
||||
return ".multiPart(${getMultipartFileParameterContent(parameter.key, (NamedProperty) parameter.value)})"
|
||||
}
|
||||
return ".param('$parameter.key', '$parameter.value')"
|
||||
}
|
||||
|
||||
protected String convertUnicodeEscapes(String json) {
|
||||
return StringEscapeUtils.unescapeJavaScript(json)
|
||||
}
|
||||
|
||||
@@ -6,16 +6,25 @@ import groovy.json.JsonOutput
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TypeChecked
|
||||
import groovy.transform.TypeCheckingMode
|
||||
import io.codearte.accurest.dsl.internal.*
|
||||
import io.codearte.accurest.dsl.internal.Body
|
||||
import io.codearte.accurest.dsl.internal.DslProperty
|
||||
import io.codearte.accurest.dsl.internal.MatchingStrategy
|
||||
import io.codearte.accurest.dsl.internal.NamedProperty
|
||||
import io.codearte.accurest.dsl.internal.QueryParameters
|
||||
import io.codearte.accurest.dsl.internal.RegexPatterns
|
||||
import io.codearte.accurest.dsl.internal.Request
|
||||
import io.codearte.accurest.util.ContentType
|
||||
import io.codearte.accurest.util.ContentUtils
|
||||
import io.codearte.accurest.util.JsonToJsonPathsConverter
|
||||
import io.codearte.accurest.util.JsonPaths
|
||||
import io.codearte.accurest.util.JsonToJsonPathsConverter
|
||||
import io.codearte.accurest.util.MapConverter
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import static io.codearte.accurest.util.ContentUtils.*
|
||||
import static io.codearte.accurest.util.ContentUtils.getEqualsTypeFromContentType
|
||||
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromContent
|
||||
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromHeader
|
||||
import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromMatchingStrategy
|
||||
import static io.codearte.accurest.util.RegexpBuilders.buildGStringRegexpForStubSide
|
||||
import static io.codearte.accurest.util.RegexpBuilders.buildJSONRegexpMatch
|
||||
|
||||
@@ -37,6 +46,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
appendUrl(requestPattern)
|
||||
appendQueryParameters(requestPattern)
|
||||
appendBody(requestPattern)
|
||||
appendMultipart(requestPattern)
|
||||
return requestPattern
|
||||
}
|
||||
|
||||
@@ -69,6 +79,22 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
requestPattern.bodyPatterns = [convertToValuePattern(getMatchingStrategy(request.body.clientValue))]
|
||||
}
|
||||
}
|
||||
|
||||
private void appendMultipart(RequestPattern requestPattern) {
|
||||
if (!request.multipart) {
|
||||
return
|
||||
}
|
||||
|
||||
if (request.multipart.clientValue instanceof Map) {
|
||||
List<ValuePattern> multipartPatterns = (request.multipart.clientValue as Map).collect {
|
||||
(it.value instanceof NamedProperty
|
||||
? ValuePattern.matches(RegexPatterns.multipartFile(it.key, (it.value as NamedProperty).name.clientValue, (it.value as NamedProperty).value.clientValue))
|
||||
: ValuePattern.matches(RegexPatterns.multipartParam(it.key, it.value)) )
|
||||
}
|
||||
|
||||
requestPattern.bodyPatterns ? requestPattern.bodyPatterns.addAll(multipartPatterns) : (requestPattern.bodyPatterns = multipartPatterns)
|
||||
}
|
||||
}
|
||||
|
||||
private void appendHeaders(RequestPattern requestPattern) {
|
||||
if(!request.headers) {
|
||||
|
||||
@@ -48,6 +48,14 @@ class Common {
|
||||
return property
|
||||
}
|
||||
|
||||
NamedProperty named(DslProperty name, DslProperty value){
|
||||
return new NamedProperty(name, value)
|
||||
}
|
||||
|
||||
NamedProperty named(Map<String, DslProperty> namedMap){
|
||||
return new NamedProperty(namedMap)
|
||||
}
|
||||
|
||||
DslProperty value(ClientDslProperty client, ServerDslProperty server) {
|
||||
assertThatSidesMatch(client.clientValue, server.serverValue)
|
||||
return new DslProperty(client.clientValue, server.serverValue)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.codearte.accurest.dsl.internal
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.ToString
|
||||
|
||||
@ToString(includePackage = false, includeFields = true, includeNames = true)
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
@CompileStatic
|
||||
class Multipart extends DslProperty {
|
||||
|
||||
Multipart(Map<String, DslProperty> multipart) {
|
||||
super(extractValue(multipart, { DslProperty p -> p.clientValue}), extractValue(multipart, {DslProperty p -> p.serverValue}))
|
||||
}
|
||||
|
||||
private static Map<String, Object> extractValue(Map<String, DslProperty> multipart, Closure valueProvider) {
|
||||
return multipart.collectEntries { Map.Entry<String, DslProperty> entry ->
|
||||
[(entry.key): valueProvider(entry.value)]
|
||||
} as Map<String, Object>
|
||||
}
|
||||
|
||||
Multipart(List<DslProperty> multipartAsList) {
|
||||
super(multipartAsList.collect { DslProperty p -> p.clientValue }, multipartAsList.collect { DslProperty p -> p.serverValue })
|
||||
}
|
||||
|
||||
Multipart(Object multipartAsValue) {
|
||||
this("${multipartAsValue}")
|
||||
}
|
||||
|
||||
Multipart(GString multipartAsValue) {
|
||||
super(multipartAsValue, multipartAsValue)
|
||||
}
|
||||
|
||||
Multipart(DslProperty multipartAsValue) {
|
||||
super(multipartAsValue.clientValue, multipartAsValue.serverValue)
|
||||
}
|
||||
|
||||
Multipart(MatchingStrategy matchingStrategy) {
|
||||
super(matchingStrategy, matchingStrategy)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.codearte.accurest.dsl.internal
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.ToString
|
||||
|
||||
@ToString(includePackage = false, includeFields = true, includeNames = true)
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
@CompileStatic
|
||||
class NamedProperty {
|
||||
|
||||
private static final String NAME = 'name'
|
||||
private static final String CONTENT = 'content'
|
||||
DslProperty name
|
||||
DslProperty value
|
||||
|
||||
NamedProperty(DslProperty name, DslProperty value) {
|
||||
this.name = name
|
||||
this.value = value
|
||||
}
|
||||
|
||||
NamedProperty(Map<String, DslProperty> namedMap) {
|
||||
this(namedMap?.get(NAME), namedMap?.get(CONTENT))
|
||||
}
|
||||
}
|
||||
@@ -43,4 +43,12 @@ class RegexPatterns {
|
||||
String url() {
|
||||
return URL.pattern()
|
||||
}
|
||||
|
||||
static String multipartParam(Object name, Object value) {
|
||||
return ".*--(.*)\r\nContent-Disposition: form-data; name=\"$name\"\r\n(Content-Type: .*\r\n)?(Content-Length: \\d+\r\n)?\r\n$value\r\n--\\1.*"
|
||||
}
|
||||
|
||||
static String multipartFile(Object name, Object filename, Object content) {
|
||||
return ".*--(.*)\r\nContent-Disposition: form-data; name=\"$name\"; filename=\"$filename\"\r\n(Content-Type: .*\r\n)?(Content-Length: \\d+\r\n)?\r\n$content\r\n--\\1.*";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class Request extends Common {
|
||||
UrlPath urlPath
|
||||
Headers headers
|
||||
Body body
|
||||
Multipart multipart
|
||||
|
||||
Request() {
|
||||
}
|
||||
@@ -24,6 +25,7 @@ class Request extends Common {
|
||||
this.urlPath = request.urlPath
|
||||
this.headers = request.headers
|
||||
this.body = request.body
|
||||
this.multipart = request.multipart
|
||||
}
|
||||
|
||||
void method(String method) {
|
||||
@@ -100,6 +102,22 @@ class Request extends Common {
|
||||
return body
|
||||
}
|
||||
|
||||
void multipart(Map<String, Object> body) {
|
||||
this.multipart = new Multipart(convertObjectsToDslProperties(body))
|
||||
}
|
||||
|
||||
void multipart(List multipartAsList) {
|
||||
this.multipart = new Multipart(convertObjectsToDslProperties(multipartAsList))
|
||||
}
|
||||
|
||||
void multipart(DslProperty dslProperty) {
|
||||
this.multipart = new Multipart(dslProperty)
|
||||
}
|
||||
|
||||
void multipart(Object multipartAsValue) {
|
||||
this.multipart = new Multipart(multipartAsValue)
|
||||
}
|
||||
|
||||
MatchingStrategy equalTo(Object value) {
|
||||
return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import io.codearte.accurest.dsl.internal.DslProperty
|
||||
import io.codearte.accurest.dsl.internal.ExecutionProperty
|
||||
import io.codearte.accurest.dsl.internal.Headers
|
||||
import io.codearte.accurest.dsl.internal.MatchingStrategy
|
||||
import io.codearte.accurest.dsl.internal.NamedProperty
|
||||
import io.codearte.accurest.dsl.internal.OptionalProperty
|
||||
import org.codehaus.groovy.runtime.GStringImpl
|
||||
|
||||
@@ -320,4 +321,8 @@ class ContentUtils {
|
||||
return ContentType.UNKNOWN
|
||||
}
|
||||
|
||||
static String getMultipartFileParameterContent(String propertyName, NamedProperty propertyValue) {
|
||||
return "'$propertyName', '$propertyValue.name.serverValue', '$propertyValue.value.serverValue'.bytes"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package io.codearte.accurest.builder
|
||||
|
||||
import io.codearte.accurest.dsl.GroovyDsl
|
||||
import io.codearte.accurest.dsl.WireMockStubStrategy
|
||||
import io.codearte.accurest.dsl.WireMockStubVerifier
|
||||
@@ -7,6 +8,7 @@ import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* @author Jakub Kubrynski
|
||||
*/
|
||||
@@ -836,4 +838,65 @@ World.''')
|
||||
World.'''""")
|
||||
}
|
||||
|
||||
@Issue('180')
|
||||
def "should generate proper test code when having multipart parameters"(){
|
||||
given:
|
||||
GroovyDsl contractDsl = GroovyDsl.make {
|
||||
request {
|
||||
method "PUT"
|
||||
url "/multipart"
|
||||
headers {
|
||||
header('content-type', 'multipart/form-data;boundary=AaB03x')
|
||||
}
|
||||
multipart(
|
||||
formParameter: value(client(regex('.+')), server('"formParameterValue"')),
|
||||
someBooleanParameter: value(client(regex('(true|false)')), server('true')),
|
||||
file: named(value(client(regex('.+')), server('filename.csv')), value(client(regex('.+')), server('file content')))
|
||||
)
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
}
|
||||
}
|
||||
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
|
||||
BlockBuilder blockBuilder = new BlockBuilder(" ")
|
||||
when:
|
||||
builder.given(blockBuilder)
|
||||
def spockTest = blockBuilder.toString()
|
||||
then:
|
||||
spockTest.contains("""'content-type', 'multipart/form-data;boundary=AaB03x'""")
|
||||
spockTest.contains(""".param('formParameter', '"formParameterValue"'""")
|
||||
spockTest.contains(""".param('someBooleanParameter', 'true')""")
|
||||
spockTest.contains(""".multiPart('file', 'filename.csv', 'file content'.bytes)""")
|
||||
}
|
||||
|
||||
@Issue('180')
|
||||
def "should generate proper test code when having multipart parameters with named as map"() {
|
||||
given:
|
||||
GroovyDsl contractDsl = GroovyDsl.make {
|
||||
request {
|
||||
method "PUT"
|
||||
url "/multipart"
|
||||
multipart(
|
||||
formParameter: value(client(regex('".+"')), server('"formParameterValue"')),
|
||||
someBooleanParameter: value(client(regex('(true|false)')), server('true')),
|
||||
file: named(
|
||||
name: value(client(regex('.+')), server('filename.csv')),
|
||||
content: value(client(regex('.+')), server('file content')))
|
||||
)
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
}
|
||||
}
|
||||
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
|
||||
BlockBuilder blockBuilder = new BlockBuilder(" ")
|
||||
when:
|
||||
builder.given(blockBuilder)
|
||||
def spockTest = blockBuilder.toString()
|
||||
then:
|
||||
spockTest.contains('.multiPart')
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1398,4 +1398,50 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
|
||||
String toWireMockClientJsonStub(groovyDsl) {
|
||||
new WireMockStubStrategy(groovyDsl).toWireMockClientStub()
|
||||
}
|
||||
|
||||
@Issue('180')
|
||||
@Unroll
|
||||
def 'should generate stub with multipart parameters'() {
|
||||
given:
|
||||
GroovyDsl contractDsl = GroovyDsl.make {
|
||||
request {
|
||||
method "PUT"
|
||||
url "/multipart"
|
||||
multipart(
|
||||
formParameter: value(client(regex('".+"')), server('"formParameterValue"')),
|
||||
someBooleanParameter: value(client(regex('(true|false)')), server('true')),
|
||||
file: named(
|
||||
name: value(client(regex('.+')), server('filename.csv')),
|
||||
content: value(client(regex('.+')), server('file content')))
|
||||
)
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
}
|
||||
}
|
||||
when:
|
||||
String wireMockStub = new WireMockStubStrategy(contractDsl).toWireMockClientStub()
|
||||
then:
|
||||
println wireMockStub
|
||||
AssertionUtil.assertThatJsonsAreEqual(('''
|
||||
{
|
||||
"request" : {
|
||||
"url" : "/multipart",
|
||||
"method" : "PUT",
|
||||
"bodyPatterns" : [ {
|
||||
"matches" : ".*--(.*)\\r\\nContent-Disposition: form-data; name=\\"formParameter\\"\\r\\n(Content-Type: .*\\r\\n)?(Content-Length: \\\\d+\\r\\n)?\\r\\n\\".+\\"\\r\\n--\\\\1.*"
|
||||
}, {
|
||||
"matches" : ".*--(.*)\\r\\nContent-Disposition: form-data; name=\\"someBooleanParameter\\"\\r\\n(Content-Type: .*\\r\\n)?(Content-Length: \\\\d+\\r\\n)?\\r\\n(true|false)\\r\\n--\\\\1.*"
|
||||
}, {
|
||||
"matches" : ".*--(.*)\\r\\nContent-Disposition: form-data; name=\\"file\\"; filename=\\".+\\"\\r\\n(Content-Type: .*\\r\\n)?(Content-Length: \\\\d+\\r\\n)?\\r\\n.+\\r\\n--\\\\1.*"
|
||||
} ]
|
||||
},
|
||||
"response" : {
|
||||
"status" : 200
|
||||
}
|
||||
}
|
||||
'''), wireMockStub)
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(wireMockStub)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user