Merge pull request #78 from dstepanov/queryParameters

DSL: Implemented urlPath and queryParameters with matching for urlPattern and urlPath

Merging as @marcingrzejszczak has already approved it.
This commit is contained in:
Olga Maciaszek-Sharma
2015-06-10 12:44:45 +02:00
11 changed files with 617 additions and 46 deletions

View File

@@ -5,6 +5,11 @@ import groovy.transform.PackageScope
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.dsl.internal.ExecutionProperty
import io.codearte.accurest.dsl.internal.Header
import io.codearte.accurest.dsl.internal.MatchingStrategy
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 java.util.regex.Pattern
@@ -20,46 +25,81 @@ class SpockMethodBodyBuilder {
}
void appendTo(BlockBuilder blockBuilder) {
blockBuilder.startBlock()
blockBuilder.addLine('given:').startBlock()
blockBuilder.addLine('def request = given()')
blockBuilder.indent()
stubDefinition.request.headers?.collect { Header header ->
blockBuilder.addLine(".header('${header.name}', '${header.serverValue}')")
}
if (stubDefinition.request.body) {
String matches = new JsonOutput().toJson(stubDefinition.request.body.serverValue)
blockBuilder.addLine(".body('$matches')")
}
blockBuilder.unindent().endBlock().addEmptyLine()
blockBuilder.addLine('when:').startBlock()
blockBuilder.addLine('def response = given().spec(request)')
blockBuilder.indent()
blockBuilder.addLine(".${stubDefinition.request.method.serverValue.toLowerCase()}(\"$stubDefinition.request.url.serverValue\")")
blockBuilder.unindent().endBlock().addEmptyLine()
blockBuilder.addLine('then:').startBlock()
blockBuilder.addLine("response.statusCode == $stubDefinition.response.status.serverValue")
stubDefinition.response.headers?.collect { Header header ->
blockBuilder.addLine("response.header('$header.name') == '$header.serverValue'")
}
if (stubDefinition.response.body) {
blockBuilder.endBlock()
blockBuilder.addLine('and:').startBlock()
blockBuilder.addLine('def responseBody = new JsonSlurper().parseText(response.body.asString())')
def responseBody = stubDefinition.response.body.serverValue
if (responseBody instanceof List) {
processArrayElements(responseBody, "", blockBuilder)
} else {
processMapElement(responseBody, blockBuilder, "")
Request request = stubDefinition.request
Response response = stubDefinition.response
blockBuilder.with {
startBlock()
addLine('given:').startBlock()
addLine('def request = given()')
indent()
request.headers?.collect { Header header ->
addLine(".header('${header.name}', '${header.serverValue}')")
}
if (request.body) {
String matches = new JsonOutput().toJson(request.body.serverValue)
addLine(".body('$matches')")
}
}
blockBuilder.endBlock()
blockBuilder.endBlock()
unindent().endBlock().addEmptyLine()
addLine('when:').startBlock()
addLine('def response = given().spec(request)')
indent()
String url = buildUrl(request)
String method = request.method.serverValue.toLowerCase()
blockBuilder.addLine(/.${method}("$url")/)
unindent().endBlock().addEmptyLine()
addLine('then:').startBlock()
addLine("response.statusCode == $response.status.serverValue")
response.headers?.collect { Header header ->
addLine("response.header('$header.name') == '$header.serverValue'")
}
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, "")
}
}
endBlock()
endBlock()
}
}
private String buildUrl(Request request) {
if (request.url)
return request.url.serverValue;
if (request.urlPath)
return buildUrlFromUrlPath(request.urlPath)
throw new IllegalStateException("URL is not set!")
}
private String buildUrlFromUrlPath(UrlPath urlPath) {
String params = urlPath.queryParameters.parameters.inject([]) { result, param ->
result << "${param.name}=${URLEncoder.encode(resolveParamValue(param).toString(), "UTF8")}"
}.join('&')
return "$urlPath.serverValue?$params"
}
private String resolveParamValue(QueryParameter param) {
resolveParamValue(param.serverValue)
}
private String resolveParamValue(Object value) {
value.toString()
}
private String resolveParamValue(MatchingStrategy matchingStrategy) {
matchingStrategy.serverValue.toString()
}
private void processBodyElement(BlockBuilder blockBuilder, String rootProperty, def element) {

View File

@@ -2,6 +2,9 @@ package io.codearte.accurest.dsl
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import io.codearte.accurest.dsl.internal.ClientRequest
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 java.util.regex.Pattern
@@ -27,12 +30,48 @@ class WiremockRequestStubStrategy extends BaseWiremockStubStrategy {
private Map<String, Object> buildRequestContent(ClientRequest request) {
return ([method : request?.method?.clientValue,
headers : buildClientRequestHeadersSection(request.headers)
] << appendUrl(request) << appendBody(request)).findAll { it.value }
] << appendUrl(request) << appendQueryParameters(request) << appendBody(request)).findAll { it.value }
}
private Map<String, Object> appendUrl(ClientRequest clientRequest) {
Object urlPath = clientRequest?.urlPath?.clientValue
if (urlPath) {
return [urlPath: urlPath]
}
Object url = clientRequest?.url?.clientValue
return url instanceof Pattern ? [urlPattern: ((Pattern)url).pattern()] : [url: url]
return url instanceof Pattern ? [urlPattern: url.pattern()] : [url: url]
}
private Map<String, Object> appendQueryParameters(ClientRequest clientRequest) {
QueryParameters queryParameters = clientRequest?.urlPath?.queryParameters ?: clientRequest?.url?.queryParameters
return queryParameters && !queryParameters.parameters.isEmpty() ?
[queryParameters: buildUrlPathQueryParameters(queryParameters)] : [:]
}
private Map<String, Object> buildUrlPathQueryParameters(QueryParameters queryParameters) {
return queryParameters.parameters.collectEntries { QueryParameter param ->
parseQueryParameter(param.name, param.clientValue)
}
}
protected Map<String, Object> parseQueryParameter(String name, MatchingStrategy matchingStrategy) {
return buildQueryParameter(name, matchingStrategy.clientValue, matchingStrategy.type)
}
protected Map<String, Object> parseQueryParameter(String name, Object value) {
return buildQueryParameter(name, value, MatchingStrategy.Type.EQUAL_TO)
}
protected Map<String, Object> parseQueryParameter(String name, Pattern pattern) {
return buildQueryParameter(name, pattern.pattern(), MatchingStrategy.Type.MATCHING)
}
private Map<String, Object> buildQueryParameter(String name, Pattern pattern, MatchingStrategy.Type type) {
return buildQueryParameter(name, pattern.pattern(), type)
}
private Map<String, Object> buildQueryParameter(String name, Object value, MatchingStrategy.Type type) {
return [(name): [(type.name) : value]]
}
private Map<String, Object> appendBody(ClientRequest clientRequest) {

View File

@@ -0,0 +1,34 @@
package io.codearte.accurest.dsl.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString;
@EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true)
@CompileStatic
class MatchingStrategy extends DslProperty {
Type type
MatchingStrategy(Object value, Type type) {
super(value)
this.type = type
}
MatchingStrategy(DslProperty value, Type type) {
super(value.clientValue, value.serverValue)
this.type = type
}
enum Type {
EQUAL_TO("equalTo"), CONTAINS("contains"), MATCHING("matches"), NOT_MATCHING("doesNotMatch")
final String name
Type(name) {
this.name = name
}
}
}

View File

@@ -0,0 +1,34 @@
package io.codearte.accurest.dsl.internal;
import groovy.transform.CompileStatic;
import groovy.transform.EqualsAndHashCode;
import groovy.transform.ToString
import static io.codearte.accurest.util.ValidateUtils.validateServerValueIsAvailable;
@EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true)
@CompileStatic
class QueryParameter extends DslProperty {
String name
QueryParameter(String name, DslProperty dslProperty) {
super(dslProperty.clientValue, dslProperty.serverValue)
validateServerValueIsAvailable(dslProperty.serverValue, "Query parameter '$name'")
this.name = name
}
QueryParameter(String name, MatchingStrategy matchingStrategy) {
super(matchingStrategy)
validateServerValueIsAvailable(matchingStrategy, "Query parameter '$name'")
this.name = name
}
QueryParameter(String name, Object value) {
super(value)
validateServerValueIsAvailable(value, "Query parameter '$name'")
this.name = name
}
}

View File

@@ -0,0 +1,39 @@
package io.codearte.accurest.dsl.internal
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
@EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true)
@TypeChecked
class QueryParameters {
List<QueryParameter> parameters = []
void parameter(Map<String, Object> singleParameter) {
Map.Entry<String, Object> first = singleParameter.entrySet().first()
parameters << new QueryParameter(first?.key, first?.value)
}
void parameter(String parameterName, Object parameterValue) {
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

@@ -11,6 +11,7 @@ class Request extends Common {
DslProperty method
Url url
UrlPath urlPath
Headers headers
Body body
@@ -20,6 +21,7 @@ class Request extends Common {
Request(Request request) {
this.method = request.method
this.url = request.url
this.urlPath = request.urlPath
this.headers = request.headers
this.body = request.body
}
@@ -32,7 +34,7 @@ class Request extends Common {
this.method = toDslProperty(method)
}
void url(String url) {
void url(Object url) {
this.url = new Url(url)
}
@@ -40,6 +42,38 @@ class Request extends Common {
this.url = new Url(url)
}
void url(Object url, @DelegatesTo(UrlPath) Closure closure) {
this.url = new Url(url)
closure.delegate = this.url
closure()
}
void url(DslProperty url, @DelegatesTo(UrlPath) Closure closure) {
this.url = new Url(url)
closure.delegate = this.url
closure()
}
void urlPath(String path) {
this.urlPath = new UrlPath(path)
}
void urlPath(DslProperty path) {
this.urlPath = new UrlPath(path)
}
void urlPath(String path, @DelegatesTo(UrlPath) Closure closure) {
this.urlPath = new UrlPath(path)
closure.delegate = urlPath
closure()
}
void urlPath(DslProperty path, @DelegatesTo(UrlPath) Closure closure) {
this.urlPath = new UrlPath(path)
closure.delegate = urlPath
closure()
}
void headers(@DelegatesTo(Headers) Closure closure) {
this.headers = new Headers()
closure.delegate = headers

View File

@@ -1,18 +1,32 @@
package io.codearte.accurest.dsl.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import static io.codearte.accurest.util.ValidateUtils.validateServerValueIsAvailable
@ToString(includePackage = false, includeFields = true, includeNames = true)
@EqualsAndHashCode(includeFields = true)
@CompileStatic
class Url extends DslProperty {
Url(DslProperty bodyAsValue) {
super(bodyAsValue.clientValue, bodyAsValue.serverValue)
QueryParameters queryParameters
Url(DslProperty prop) {
super(prop.clientValue, prop.serverValue)
validateServerValueIsAvailable(prop.serverValue, "Url")
}
Url(String bodyAsValue) {
super(bodyAsValue)
Url(Object url) {
super(url)
validateServerValueIsAvailable(url, "Url")
}
void queryParameters(@DelegatesTo(QueryParameters) Closure closure) {
this.queryParameters = new QueryParameters()
closure.delegate = queryParameters
closure()
}
}

View File

@@ -0,0 +1,20 @@
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 UrlPath extends Url {
UrlPath(String path) {
super(path)
}
UrlPath(DslProperty path) {
super(path)
}
}

View File

@@ -0,0 +1,41 @@
package io.codearte.accurest.util
import groovy.transform.TypeChecked
import io.codearte.accurest.dsl.internal.DslProperty
import io.codearte.accurest.dsl.internal.MatchingStrategy
import java.util.regex.Pattern
@TypeChecked
class ValidateUtils {
static Object validateServerValueIsAvailable(Object value) {
validateServerValueIsAvailable(value, "Server value")
return value
}
static Object validateServerValueIsAvailable(Object value, String msg) {
validateServerValue(value, msg)
return value
}
static void validateServerValue(Pattern pattern, String msg) {
throw new IllegalStateException("$msg can't be a pattern for the server side")
}
static void validateServerValue(MatchingStrategy matchingStrategy, String msg) {
if (matchingStrategy.type != MatchingStrategy.Type.EQUAL_TO) {
throw new IllegalStateException("$msg can't be of a matching type: $matchingStrategy.type for the server side")
}
validateServerValue(matchingStrategy.serverValue, msg)
}
static void validateServerValue(DslProperty value, String msg) {
validateServerValue(value.serverValue, msg)
}
static void validateServerValue(Object value, String msg) {
// OK
}
}

View File

@@ -169,4 +169,42 @@ class SpockMethodBuilderSpec extends Specification {
blockBuilder.toString().contains("responseBody.property2 ==~ java.util.regex.Pattern.compile('[0-9]{3}')")
}
def "should generate a call with an url path and query parameters"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method 'GET'
urlPath('/users') {
queryParameters {
parameter 'limit': $(client(equalTo("20")), server(equalTo("10")))
parameter 'offset': $(client(containing("20")), server(equalTo("20")))
parameter 'filter': "email"
parameter 'sort': equalTo("name")
parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server("55"))
parameter 'age': $(client(notMatching("^\\w*\$")), server("99"))
parameter 'name': $(client(matching("Denis.*")), server("Denis.Stepanov"))
}
}
}
response {
status 200
body """
{
"property1": "a",
"property2": "b"
}
"""
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
def spockTest = blockBuilder.toString()
then:
spockTest.contains('get("/users?limit=10&offset=20&filter=email&sort=name&search=55&age=99&name=Denis.Stepanov")')
spockTest.contains('responseBody.property1 == "a"')
spockTest.contains('responseBody.property2 == "b"')
}
}

View File

@@ -1,4 +1,6 @@
package io.codearte.accurest.dsl
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
class WiremockGroovyDslSpec extends WiremockSpec {
@@ -313,6 +315,230 @@ class WiremockGroovyDslSpec extends WiremockSpec {
''')
}
def "should generate request with urlPath and queryParameters for client side"() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
request {
method 'GET'
urlPath($(client("users"), server("items"))) {
queryParameters {
parameter 'limit': $(client(equalTo("20")), server("10"))
parameter 'offset': $(client(containing("10")), server("10"))
parameter 'filter': "email"
parameter 'sort': $(client(~/^[0-9]{10}$/), server("1234567890"))
parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server("10"))
parameter 'age': $(client(notMatching("^\\w*\$")), server(10))
parameter 'name': $(client(matching("Denis.*")), server("Denis"))
}
}
}
response {
status 200
}
}
when:
def json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"urlPath": "users",
"queryParameters": {
"offset": {
"contains": "10"
},
"limit": {
"equalTo": "20"
},
"filter": {
"equalTo": "email"
},
"sort": {
"matches": "^[0-9]{10}$"
},
"search": {
"doesNotMatch": "^/[0-9]{2}$"
},
"age": {
"doesNotMatch": "^\\\\w*$"
},
"name": {
"matches": "Denis.*"
}
}
},
"response": {
"status": 200,
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
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,
}
}
''')
and:
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,
}
}
''')
and:
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.*")
}
}
}
response {
status 200
}
}
then:
def e = thrown(IllegalStateException)
e.message.contains "Url can't be a pattern for the server side"
}
def "should not allow regexp in query parameter for server value"() {
when:
GroovyDsl.make {
request {
method 'GET'
url("abc") {
queryParameters {
parameter 'age': $(client(notMatching("^\\w*\$")), server(regex(".*")))
}
}
}
response {
status 200
}
}
then:
def e = thrown(IllegalStateException)
e.message.contains "Query parameter 'age' can't be a pattern for the server side"
}
def "should not allow query parameter unresolvable for a server value"() {
when:
GroovyDsl.make {
request {
method 'GET'
urlPath("users") {
queryParameters {
parameter 'age': notMatching("^\\w*\$")
parameter 'name': matching("Denis.*")
}
}
}
response {
status 200
}
}
then:
def e = thrown(IllegalStateException)
e.message.contains "Query parameter 'age' can't be of a matching type: NOT_MATCHING for the server side"
}
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"))
}
}
}
response {
status 200
}
}
when:
def json = toWiremockClientJsonStub(groovyDsl)
then:
parseJson(json) == parseJson('''
{
"request": {
"method": "GET",
"urlPattern": "users/[0-9]*",
"queryParameters": {
"age": {
"doesNotMatch": "^\\\\w*$"
},
"name": {
"matches": "Denis.*"
}
}
},
"response": {
"status": 200,
}
}
''')
and:
stubMappingIsValidWiremockStub(json)
}
def "should generate stub with some headers section for client side"() {
given:
GroovyDsl groovyDsl = GroovyDsl.make {
@@ -347,4 +573,16 @@ class WiremockGroovyDslSpec extends WiremockSpec {
}
''')
}
String toJsonString(value) {
new JsonBuilder(value).toPrettyString()
}
Object parseJson(json) {
new JsonSlurper().parseText(json)
}
String toWiremockClientJsonStub(groovyDsl) {
new WiremockStubStrategy(groovyDsl).toWiremockClientStub()
}
}