Merge pull request #181 from RemboL/tech/add-multipart-named-parameter

[#180] add multipart support
This commit is contained in:
Olga Maciaszek-Sharma
2015-12-02 14:15:10 +01:00
11 changed files with 259 additions and 4 deletions

View File

@@ -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()
}

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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))
}
}

View File

@@ -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.*";
}
}

View File

@@ -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)
}

View File

@@ -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"
}
}

View File

@@ -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')
}
}

View File

@@ -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)
}
}