From 194d4a034a36871a69b3bb8e3c66386e4682c6a5 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Fri, 5 Jun 2015 12:49:55 +0200 Subject: [PATCH 1/6] Implemented queryParameters matching for urlPattern and urlPath --- .../dsl/WiremockRequestStubStrategy.groovy | 44 ++++++- .../dsl/internal/MatchingStrategy.groovy | 34 +++++ .../dsl/internal/QueryParameter.groovy | 29 +++++ .../dsl/internal/QueryParameters.groovy | 45 +++++++ .../accurest/dsl/internal/Request.groovy | 36 +++++- .../codearte/accurest/dsl/internal/Url.groovy | 20 ++- .../accurest/dsl/internal/UrlPath.groovy | 20 +++ .../accurest/dsl/WiremockGroovyDslSpec.groovy | 118 ++++++++++++++++++ 8 files changed, 337 insertions(+), 9 deletions(-) create mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/MatchingStrategy.groovy create mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy create mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameters.groovy create mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/UrlPath.groovy diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy index d6854255d9..aa0b0f6bbc 100755 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy @@ -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,47 @@ class WiremockRequestStubStrategy extends BaseWiremockStubStrategy { private Map 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 appendUrl(ClientRequest clientRequest) { - Object url = clientRequest?.url?.clientValue - return url instanceof Pattern ? [urlPattern: ((Pattern)url).pattern()] : [url: url] + def urlPath = clientRequest?.urlPath?.clientValue + if (urlPath) { + return [urlPath: urlPath] + } + def url = clientRequest?.url?.clientValue + return url instanceof Pattern ? [urlPattern: url.pattern()] : [url: url] + } + + private Map appendQueryParameters(ClientRequest clientRequest) { + def queryParameters = clientRequest?.urlPath?.queryParameters ?: clientRequest?.url?.queryParameters + return queryParameters && !queryParameters.parameters.isEmpty() ? + [queryParameters: buildUrlPathQueryParameters(queryParameters)] : [:] + } + + private Map buildUrlPathQueryParameters(QueryParameters queryParameters) { + return queryParameters.parameters.collectEntries { QueryParameter param -> + parseQueryParameter(param.name, param.clientValue) + } + } + + protected Map parseQueryParameter(String name, MatchingStrategy matchingStrategy) { + return buildQueryParameter(name, matchingStrategy.clientValue, matchingStrategy.type) + } + + protected Map parseQueryParameter(String name, Object value) { + return buildQueryParameter(name, value, MatchingStrategy.Type.EQUAL_TO) + } + + protected Map parseQueryParameter(String name, Pattern pattern) { + return buildQueryParameter(name, pattern.pattern(), MatchingStrategy.Type.MATCHING) + } + + private Map buildQueryParameter(String name, Object value, MatchingStrategy.Type type) { + if (value instanceof Pattern) { + value = value.pattern() + } + return [(name): [(type.name) : value]] } private Map appendBody(ClientRequest clientRequest) { diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/MatchingStrategy.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/MatchingStrategy.groovy new file mode 100644 index 0000000000..2be3567482 --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/MatchingStrategy.groovy @@ -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 +public 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 + } + } + +} \ No newline at end of file diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy new file mode 100644 index 0000000000..2e5a606c29 --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy @@ -0,0 +1,29 @@ +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 +public class QueryParameter extends DslProperty { + + String name + + QueryParameter(String name, DslProperty dslProperty) { + super(dslProperty.clientValue, dslProperty.serverValue) + this.name = name + } + + QueryParameter(String name, MatchingStrategy matchingStrategy) { + super(matchingStrategy) + this.name = name + } + + QueryParameter(String name, Object value) { + super(value) + this.name = name + } + +} diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameters.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameters.groovy new file mode 100644 index 0000000000..c233681652 --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameters.groovy @@ -0,0 +1,45 @@ +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 +public class QueryParameters { + + List parameters = [] + + public void parameter(Map singleParameter) { + Map.Entry first = singleParameter.entrySet().first() + parameters << new QueryParameter(first?.key, first?.value) + } + + public void parameter(String parameterName, Object parameterValue) { + parameters << new QueryParameter(parameterName, parameterValue) + } + + def equalTo(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO) + } + + def containing(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.CONTAINS) + } + + def matching(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.MATCHING) + } + + def notMatching(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.NOT_MATCHING) + } + + void collect(Closure closure) { + parameters?.each { + parameter -> closure(parameter) + } + } + +} diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Request.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Request.groovy index 50bd42910c..dab8f873f2 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Request.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Request.groovy @@ -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 diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Url.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Url.groovy index da91b4a248..42a26eb26c 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Url.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Url.groovy @@ -1,18 +1,28 @@ 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 Url extends DslProperty { - Url(DslProperty bodyAsValue) { - super(bodyAsValue.clientValue, bodyAsValue.serverValue) + QueryParameters queryParameters + + Url(DslProperty prop) { + super(prop.clientValue, prop.serverValue) } - Url(String bodyAsValue) { - super(bodyAsValue) + Url(Object url) { + super(url) } - + + void queryParameters(@DelegatesTo(QueryParameters) Closure closure) { + this.queryParameters = new QueryParameters() + closure.delegate = queryParameters + closure() + } + } diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/UrlPath.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/UrlPath.groovy new file mode 100644 index 0000000000..ee1d3eed98 --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/UrlPath.groovy @@ -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) + } + +} diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy index f5b61625e3..ba6211223d 100755 --- a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy @@ -1,4 +1,6 @@ package io.codearte.accurest.dsl + +import groovy.json.JsonBuilder import groovy.json.JsonSlurper class WiremockGroovyDslSpec extends WiremockSpec { @@ -313,6 +315,110 @@ 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(containing("10"))) + parameter 'offset': containing("10") + parameter 'filter': "email" + parameter 'sort': ~/^[0-9]{10}$/ + parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server(containing("10"))) + parameter 'age': notMatching("^\\w*\$") + parameter 'name': matching("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 url and queryParameters for client side"() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + method 'GET' + url(regex(/users\/[0-9]*/)) { + queryParameters { + parameter 'age': notMatching("^\\w*\$") + parameter 'name': matching("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 +453,16 @@ class WiremockGroovyDslSpec extends WiremockSpec { } ''') } + + def toJsonString(value) { + new JsonBuilder(value).toPrettyString() + } + + def parseJson(json) { + new JsonSlurper().parseText(json) + } + + def toWiremockClientJsonStub(groovyDsl) { + new WiremockStubStrategy(groovyDsl).toWiremockClientStub() + } } From 1eb035189e3915e1ba9c84be58e3213584884a62 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Fri, 5 Jun 2015 12:56:58 +0200 Subject: [PATCH 2/6] Add more tests --- .../accurest/dsl/WiremockGroovyDslSpec.groovy | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy index ba6211223d..6e3843e146 100755 --- a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy @@ -343,7 +343,7 @@ class WiremockGroovyDslSpec extends WiremockSpec { { "request": { "method": "GET", - "urlPath":"users", + "urlPath": "users", "queryParameters": { "offset": { "contains": "10" @@ -377,6 +377,64 @@ class WiremockGroovyDslSpec extends WiremockSpec { 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 generate request with url and queryParameters for client side"() { given: GroovyDsl groovyDsl = GroovyDsl.make { From cd226a009dec20f7d630ce4e4307bb385c50a29b Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Mon, 8 Jun 2015 13:29:21 +0200 Subject: [PATCH 3/6] PR code review comments --- .../dsl/WiremockRequestStubStrategy.groovy | 23 ++++++++++--------- .../dsl/internal/MatchingStrategy.groovy | 2 +- .../dsl/internal/QueryParameter.groovy | 2 +- .../dsl/internal/QueryParameters.groovy | 20 ++++++---------- .../accurest/dsl/WiremockGroovyDslSpec.groovy | 6 ++--- 5 files changed, 24 insertions(+), 29 deletions(-) diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy index aa0b0f6bbc..298cc1b2ec 100755 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy @@ -34,42 +34,43 @@ class WiremockRequestStubStrategy extends BaseWiremockStubStrategy { } private Map appendUrl(ClientRequest clientRequest) { - def urlPath = clientRequest?.urlPath?.clientValue + Object urlPath = clientRequest?.urlPath?.clientValue if (urlPath) { return [urlPath: urlPath] } - def url = clientRequest?.url?.clientValue + Object url = clientRequest?.url?.clientValue return url instanceof Pattern ? [urlPattern: url.pattern()] : [url: url] } private Map appendQueryParameters(ClientRequest clientRequest) { - def queryParameters = clientRequest?.urlPath?.queryParameters ?: clientRequest?.url?.queryParameters + QueryParameters queryParameters = clientRequest?.urlPath?.queryParameters ?: clientRequest?.url?.queryParameters return queryParameters && !queryParameters.parameters.isEmpty() ? [queryParameters: buildUrlPathQueryParameters(queryParameters)] : [:] } - private Map buildUrlPathQueryParameters(QueryParameters queryParameters) { + private Map buildUrlPathQueryParameters(QueryParameters queryParameters) { return queryParameters.parameters.collectEntries { QueryParameter param -> parseQueryParameter(param.name, param.clientValue) } } - protected Map parseQueryParameter(String name, MatchingStrategy matchingStrategy) { + protected Map parseQueryParameter(String name, MatchingStrategy matchingStrategy) { return buildQueryParameter(name, matchingStrategy.clientValue, matchingStrategy.type) } - protected Map parseQueryParameter(String name, Object value) { + protected Map parseQueryParameter(String name, Object value) { return buildQueryParameter(name, value, MatchingStrategy.Type.EQUAL_TO) } - protected Map parseQueryParameter(String name, Pattern pattern) { + protected Map parseQueryParameter(String name, Pattern pattern) { return buildQueryParameter(name, pattern.pattern(), MatchingStrategy.Type.MATCHING) } - private Map buildQueryParameter(String name, Object value, MatchingStrategy.Type type) { - if (value instanceof Pattern) { - value = value.pattern() - } + private Map buildQueryParameter(String name, Pattern pattern, MatchingStrategy.Type type) { + return buildQueryParameter(name, pattern.pattern(), type) + } + + private Map buildQueryParameter(String name, Object value, MatchingStrategy.Type type) { return [(name): [(type.name) : value]] } diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/MatchingStrategy.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/MatchingStrategy.groovy index 2be3567482..c385bb1939 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/MatchingStrategy.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/MatchingStrategy.groovy @@ -7,7 +7,7 @@ import groovy.transform.ToString; @EqualsAndHashCode(includeFields = true) @ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) @CompileStatic -public class MatchingStrategy extends DslProperty { +class MatchingStrategy extends DslProperty { Type type diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy index 2e5a606c29..78d8e34009 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy @@ -7,7 +7,7 @@ import groovy.transform.ToString; @EqualsAndHashCode(includeFields = true) @ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) @CompileStatic -public class QueryParameter extends DslProperty { +class QueryParameter extends DslProperty { String name diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameters.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameters.groovy index c233681652..eedaf5654b 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameters.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameters.groovy @@ -7,39 +7,33 @@ import groovy.transform.TypeChecked @EqualsAndHashCode(includeFields = true) @ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) @TypeChecked -public class QueryParameters { +class QueryParameters { List parameters = [] - public void parameter(Map singleParameter) { + void parameter(Map singleParameter) { Map.Entry first = singleParameter.entrySet().first() parameters << new QueryParameter(first?.key, first?.value) } - public void parameter(String parameterName, Object parameterValue) { + void parameter(String parameterName, Object parameterValue) { parameters << new QueryParameter(parameterName, parameterValue) } - def equalTo(Object value) { + MatchingStrategy equalTo(Object value) { return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO) } - def containing(Object value) { + MatchingStrategy containing(Object value) { return new MatchingStrategy(value, MatchingStrategy.Type.CONTAINS) } - def matching(Object value) { + MatchingStrategy matching(Object value) { return new MatchingStrategy(value, MatchingStrategy.Type.MATCHING) } - def notMatching(Object value) { + MatchingStrategy notMatching(Object value) { return new MatchingStrategy(value, MatchingStrategy.Type.NOT_MATCHING) } - void collect(Closure closure) { - parameters?.each { - parameter -> closure(parameter) - } - } - } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy index 6e3843e146..de857e0efc 100755 --- a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy @@ -512,15 +512,15 @@ class WiremockGroovyDslSpec extends WiremockSpec { ''') } - def toJsonString(value) { + String toJsonString(value) { new JsonBuilder(value).toPrettyString() } - def parseJson(json) { + Object parseJson(json) { new JsonSlurper().parseText(json) } - def toWiremockClientJsonStub(groovyDsl) { + String toWiremockClientJsonStub(groovyDsl) { new WiremockStubStrategy(groovyDsl).toWiremockClientStub() } } From 2420ea7ad713fe3c7f92b85a4503888352824fb6 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Tue, 9 Jun 2015 13:20:00 +0200 Subject: [PATCH 4/6] Spock generation for url path --- .../builder/SpockMethodBodyBuilder.groovy | 116 ++++++++++++------ .../builder/SpockMethodBuilderSpec.groovy | 38 ++++++ 2 files changed, 116 insertions(+), 38 deletions(-) diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/builder/SpockMethodBodyBuilder.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/builder/SpockMethodBodyBuilder.groovy index 532e429952..41734b0a3a 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/builder/SpockMethodBodyBuilder.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/builder/SpockMethodBodyBuilder.groovy @@ -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) { diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/builder/SpockMethodBuilderSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/builder/SpockMethodBuilderSpec.groovy index 0bde0a9ea3..ce1d00fa71 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/builder/SpockMethodBuilderSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/builder/SpockMethodBuilderSpec.groovy @@ -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"') + } + } From c0580dfe7a8db3facb73a71474575dd6e638075c Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Tue, 9 Jun 2015 17:40:04 +0200 Subject: [PATCH 5/6] Add server values validation --- .../dsl/internal/QueryParameter.groovy | 7 +- .../codearte/accurest/dsl/internal/Url.groovy | 4 + .../accurest/util/ValidateUtils.groovy | 39 +++++++++ .../accurest/dsl/WiremockGroovyDslSpec.groovy | 80 ++++++++++++++++--- 4 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/util/ValidateUtils.groovy diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy index 78d8e34009..042da081f2 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/QueryParameter.groovy @@ -2,7 +2,9 @@ package io.codearte.accurest.dsl.internal; import groovy.transform.CompileStatic; import groovy.transform.EqualsAndHashCode; -import groovy.transform.ToString; +import groovy.transform.ToString + +import static io.codearte.accurest.util.ValidateUtils.validateServerValueIsAvailable; @EqualsAndHashCode(includeFields = true) @ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) @@ -13,16 +15,19 @@ class QueryParameter extends DslProperty { 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 } diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Url.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Url.groovy index 42a26eb26c..09eae6d094 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Url.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Url.groovy @@ -4,6 +4,8 @@ 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 @@ -13,10 +15,12 @@ class Url extends DslProperty { Url(DslProperty prop) { super(prop.clientValue, prop.serverValue) + validateServerValueIsAvailable(prop.serverValue, "Url") } Url(Object url) { super(url) + validateServerValueIsAvailable(url, "Url") } void queryParameters(@DelegatesTo(QueryParameters) Closure closure) { diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/ValidateUtils.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/util/ValidateUtils.groovy new file mode 100644 index 0000000000..9c07eaa3e4 --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/ValidateUtils.groovy @@ -0,0 +1,39 @@ +package io.codearte.accurest.util + +import io.codearte.accurest.dsl.internal.DslProperty +import io.codearte.accurest.dsl.internal.MatchingStrategy + +import java.util.regex.Pattern + +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") + } + + static void validateServerValue(MatchingStrategy matchingStrategy, String msg) { + if (matchingStrategy.type != MatchingStrategy.Type.EQUAL_TO) { + throw new IllegalStateException("$msg can't be of matching type: $matchingStrategy.type") + } + validateServerValue(matchingStrategy.serverValue, msg) + } + + static void validateServerValue(DslProperty value, String msg) { + validateServerValue(value.serverValue, msg) + } + + static void validateServerValue(Object value, String msg) { + // OK + } + +} diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy index de857e0efc..e5e33d9e04 100755 --- a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy @@ -322,13 +322,13 @@ class WiremockGroovyDslSpec extends WiremockSpec { method 'GET' urlPath($(client("users"), server("items"))) { queryParameters { - parameter 'limit': $(client(equalTo("20")), server(containing("10"))) - parameter 'offset': containing("10") + parameter 'limit': $(client(equalTo("20")), server("10")) + parameter 'offset': $(client(containing("10")), server("10")) parameter 'filter': "email" - parameter 'sort': ~/^[0-9]{10}$/ - parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server(containing("10"))) - parameter 'age': notMatching("^\\w*\$") - parameter 'name': matching("Denis.*") + 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")) } } } @@ -435,9 +435,9 @@ class WiremockGroovyDslSpec extends WiremockSpec { stubMappingIsValidWiremockStub(json) } - def "should generate request with url and queryParameters for client side"() { - given: - GroovyDsl groovyDsl = GroovyDsl.make { + def "should not allow regexp in url for server value"() { + when: + GroovyDsl.make { request { method 'GET' url(regex(/users\/[0-9]*/)) { @@ -451,6 +451,68 @@ class WiremockGroovyDslSpec extends WiremockSpec { status 200 } } + then: + def e = thrown(IllegalStateException) + e.message.contains "Url can't be a pattern" + } + + 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" + } + + 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 matching type: NOT_MATCHING" + } + + 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: From 86efa5f242c10c6c24eaa91f6309ea15d123af05 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 10 Jun 2015 10:53:11 +0200 Subject: [PATCH 6/6] Code review changes --- .../accurest/util/ValidateUtils.groovy | 6 +- .../builder/SpockMethodBuilderSpec.groovy | 56 +++++++++---------- .../accurest/dsl/WiremockGroovyDslSpec.groovy | 6 +- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/ValidateUtils.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/util/ValidateUtils.groovy index 9c07eaa3e4..eac7bce25c 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/util/ValidateUtils.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/ValidateUtils.groovy @@ -1,10 +1,12 @@ 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) { @@ -18,12 +20,12 @@ class ValidateUtils { } static void validateServerValue(Pattern pattern, String msg) { - throw new IllegalStateException("$msg can't be a pattern") + 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 matching type: $matchingStrategy.type") + throw new IllegalStateException("$msg can't be of a matching type: $matchingStrategy.type for the server side") } validateServerValue(matchingStrategy.serverValue, msg) } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/builder/SpockMethodBuilderSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/builder/SpockMethodBuilderSpec.groovy index ce1d00fa71..0aff49357f 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/builder/SpockMethodBuilderSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/builder/SpockMethodBuilderSpec.groovy @@ -171,40 +171,40 @@ class SpockMethodBuilderSpec extends Specification { 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")) + 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" + response { + status 200 + body """ + { + "property1": "a", + "property2": "b" + } + """ } - """ } - } - SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def spockTest = blockBuilder.toString() + 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"') + 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"') } } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy index e5e33d9e04..7d8e5f1d49 100755 --- a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy @@ -453,7 +453,7 @@ class WiremockGroovyDslSpec extends WiremockSpec { } then: def e = thrown(IllegalStateException) - e.message.contains "Url can't be a pattern" + e.message.contains "Url can't be a pattern for the server side" } def "should not allow regexp in query parameter for server value"() { @@ -473,7 +473,7 @@ class WiremockGroovyDslSpec extends WiremockSpec { } then: def e = thrown(IllegalStateException) - e.message.contains "Query parameter 'age' can't be a pattern" + 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"() { @@ -494,7 +494,7 @@ class WiremockGroovyDslSpec extends WiremockSpec { } then: def e = thrown(IllegalStateException) - e.message.contains "Query parameter 'age' can't be of matching type: NOT_MATCHING" + 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"() {