diff --git a/accurest-converters/src/main/groovy/io/codearte/accurest/wiremock/WiremockToDslConverter.groovy b/accurest-converters/src/main/groovy/io/codearte/accurest/wiremock/WiremockToDslConverter.groovy index cdd09eee72..101260ac3a 100644 --- a/accurest-converters/src/main/groovy/io/codearte/accurest/wiremock/WiremockToDslConverter.groovy +++ b/accurest-converters/src/main/groovy/io/codearte/accurest/wiremock/WiremockToDslConverter.groovy @@ -1,37 +1,138 @@ package io.codearte.accurest.wiremock +import groovy.io.FileType +import groovy.json.JsonOutput import groovy.json.JsonSlurper -import io.coderate.accurest.dsl.GroovyDsl +import groovy.xml.XmlUtil + +import static org.apache.commons.lang3.StringEscapeUtils.escapeJava class WiremockToDslConverter { - static GroovyDsl fromWiremockStub(String wiremockStringStub) { + static String fromWiremockStub(String wiremockStringStub) { + return new WiremockToDslConverter().convertFromWiremockStub(wiremockStringStub) + } + + private String convertFromWiremockStub(String wiremockStringStub) { Object wiremockStub = new JsonSlurper().parseText(wiremockStringStub) - def wiremockRequest = wiremockStub.request - def wiremockResponse = wiremockStub.response - return GroovyDsl.make { + def request = wiremockStub.request + def response = wiremockStub.response + return """\ request { - wiremockRequest.method ? method(wiremockRequest.method as String) : null - wiremockRequest.url ? url(wiremockRequest.url as String) : null - wiremockRequest.urlPattern ? urlPattern(wiremockRequest.urlPattern as String) : null - wiremockRequest.urlPath ? urlPath(wiremockRequest.urlPath as String) : null - wiremockRequest.headers ? headers { - wiremockRequest.headers.each { + ${request.method ? "method \"\"\"$request.method\"\"\"" : ""} + ${request.url ? "url \"\"\"$request.url\"\"\"" : ""} + ${request.urlPattern ? "urlPattern \"\"\"${escapeJava(request.urlPattern)}\"\"\"" : ""} + ${request.urlPath ? "urlPath \"\"\"$request.urlPath\"\"\"" : ""} + ${request.headers ? """headers { + ${request.headers.collect { def assertion = it.value String headerName = it.key as String - header(headerName)."$assertion.key"(assertion.value) - } - } : null + def entry = assertion.entrySet().first() + """header(\"\"\"$headerName\"\"\").$entry.key(\"\"\"${escapeJava(entry.value)}\"\"\")\n""" + }.join('') + } + } + """ : ""} } response { - status wiremockResponse.status ? wiremockResponse.status as Integer : null - wiremockResponse.body ? body ( - wiremockResponse.body as Map - ) : null - wiremockResponse.headers ? headers { - wiremockResponse.headers.each { - header([(it.key) : it.value]) + ${response.status ? "status $response.status" : ""} + ${response.body ? "body( ${buildBody(response.body)})" : ""} + ${response.headers ? """headers { + ${response.headers.collect { "header('$it.key': '${it.value}')\n" }.join('')} } - } : null + """ : ""} + } + """ + } + + private Object buildBody(Map responseBody) { + return responseBody.entrySet().collectAll(withQuotedMapStringElements()).inject([:], appendToIterable()) + } + + private Object buildBody(List responseBody) { + return responseBody.collectAll(withQuotedStringElements()).inject([], appendToIterable()) + } + + private Object buildBody(Integer responseBody) { + return responseBody + } + + private Object buildBody(String responseBody) { + try { + def json = new JsonSlurper().parseText(responseBody) + return wrapWithMultilineGString(JsonOutput.prettyPrint(responseBody)) + } catch (Exception jsonException) { + try { + def xml = new XmlSlurper().parseText(responseBody) + return wrapWithMultilineGString(XmlUtil.serialize(responseBody)) + } catch (Exception xmlException) { + return wrapWithMultilineGString(responseBody) } } } + + private String wrapWithMultilineGString(String string) { + return """\"\"\"$string\"\"\"""" + } + + private Closure withQuotedMapStringElements() { + return { + [(it.key): convert(it.value)] + } + } + + private Closure withQuotedStringElements() { + return { + convert(it) + } + } + + private Closure appendToIterable() { + return { + acc, el -> acc << el + } + } + + private Object convert(Object element) { + return element + } + + private Object convert(String element) { + return quoteString(element) + } + + private String quoteString(String element) { + if (element =~ /^".*"$/) { + return element + } + return """\"\"\"${escapeJava(element)}\"\"\"""" + } + + private Object convert(List element) { + return element.collect { + convert(it) + } + } + + private Object convert(Map element) { + return element.collectEntries { + [(it.key) : convert(it.value)] + } + } + + static void main(String[] args) { + String rootOfFolderWithStubs = args[0] + new File(rootOfFolderWithStubs).eachFileRecurse(FileType.FILES) { + try { + if(!it.name.endsWith('json')) { + return + } + String wiremockStub = fromWiremockStub(it.text) + File newGroovyFile = new File(it.parent, it.name.replaceAll('json', 'groovy')) + println("Creating new groovy file [$newGroovyFile.path]") + newGroovyFile.text = wiremockStub + } catch (Exception e) { + System.err.println(e) + } + + } + } } diff --git a/accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/WiremockToDslConverterSpec.groovy b/accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/WiremockToDslConverterSpec.groovy index 6a4534a20b..aac2bbb12f 100644 --- a/accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/WiremockToDslConverterSpec.groovy +++ b/accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/WiremockToDslConverterSpec.groovy @@ -7,22 +7,92 @@ class WiremockToDslConverterSpec extends Specification { def 'should produce a Groovy DSL from Wiremock stub'() { given: - String wiremockStub = ''' + String wiremockStub = '''\ { "request": { "method": "GET", - "urlPattern": "/[0-9]{2}" + "urlPattern": "/[0-9]{2}", + "headers" : { + "Accept": { + "matches": "text/.*" + }, + "etag": { + "doesNotMatch": "abcd.*" + }, + "X-Custom-Header": { + "contains": "2134" + } + } }, "response": { "status": 200, "body": { - "id": "123", + "id": { + "value": "132" + }, "surname": "Kowalsky", "name": "Jan", "created" : "2014-02-02 12:23:43" }, "headers": { - "Content-Type": "text/plain" + "Content-Type": "text/plain", + } + } +} +''' + and: + GroovyDsl expectedGroovyDsl = GroovyDsl.make { + request { + method 'GET' + urlPattern '/[0-9]{2}' + headers { + header('Accept').matches('text/.*') + header('etag').doesNotMatch('abcd.*') + header('X-Custom-Header').contains('2134') + } + } + response { + status 200 + body ( + id : [value: '132'], + surname : 'Kowalsky', + name: 'Jan', + created : '2014-02-02 12:23:43' + ) + headers { + header 'Content-Type': 'text/plain' + + } + } + } + when: + String groovyDsl = WiremockToDslConverter.fromWiremockStub(wiremockStub) + then: + new GroovyShell(this.class.classLoader).evaluate( + """ io.coderate.accurest.dsl.GroovyDsl.make { + $groovyDsl + }""") == expectedGroovyDsl + } + + + def 'should convert Wiremock stub with body containing simple JSON'() { + given: + String wiremockStub = '''\ +{ + "request": { + "method": "DELETE", + "urlPattern": "/credit-card-verification-data/[0-9]+", + "headers": { + "Content-Type": { + "equalTo": "application/vnd.mymoid-adapter.v2+json; charset=UTF-8" + } + } + }, + "response": { + "status": 200, + "body": "{\\"status\\": \\"OK\\"}", + "headers": { + "Content-Type": "application/json" } } } @@ -30,25 +100,200 @@ class WiremockToDslConverterSpec extends Specification { and: GroovyDsl expectedGroovyDsl = GroovyDsl.make { request { - method 'GET' - urlPattern '/[0-9]{2}' + method 'DELETE' + urlPattern '/credit-card-verification-data/[0-9]+' + headers { + header('Content-Type').equalTo('application/vnd.mymoid-adapter.v2+json; charset=UTF-8') + } } response { status 200 - body ( - id : '123', - surname : 'Kowalsky', - name: 'Jan', - created : '2014-02-02 12:23:43' - ) + body ("""{ + "status": "OK" +}""") headers { - header 'Content-Type': 'text/plain' + header 'Content-Type': 'application/json' + } } } when: - GroovyDsl groovyDsl = WiremockToDslConverter.fromWiremockStub(wiremockStub) + String groovyDsl = WiremockToDslConverter.fromWiremockStub(wiremockStub) then: - groovyDsl == expectedGroovyDsl + new GroovyShell(this.class.classLoader).evaluate( + """ io.coderate.accurest.dsl.GroovyDsl.make { + $groovyDsl + }""") == expectedGroovyDsl + } + + def 'should convert Wiremock stub with body containing integer'() { + given: + String wiremockStub = '''\ +{ + "request": { + "method": "POST", + "url": "/charge/count", + "headers": { + "Content-Type": { + "equalTo": "application/vnd.creditcard-reporter.v1+json" + } + } + }, + "response": { + "status": 200, + "body": 200, + "headers": { + "Content-Type": "application/json" + } + } +} +''' + and: + GroovyDsl expectedGroovyDsl = GroovyDsl.make { + request { + method 'POST' + url '/charge/count' + headers { + header('Content-Type').equalTo('application/vnd.creditcard-reporter.v1+json') + } + } + response { + status 200 + body ( 200 ) + headers { + header 'Content-Type': 'application/json' + + } + } + } + when: + String groovyDsl = WiremockToDslConverter.fromWiremockStub(wiremockStub) + then: + new GroovyShell(this.class.classLoader).evaluate( + """ io.coderate.accurest.dsl.GroovyDsl.make { + $groovyDsl + }""") == expectedGroovyDsl + } + + def 'should convert Wiremock stub with body as a list'() { + given: + String wiremockStub = '''\ +{ + "request": { + "method": "POST", + "url": "/charge/count", + "headers": { + "Content-Type": { + "equalTo": "application/vnd.creditcard-reporter.v1+json" + } + } + }, + "response": { + "status": 200, + "body": [ + {"a":1, "c":"3"}, + "b", + "a" + ], + "headers": { + "Content-Type": "application/json" + } + } +} +''' + and: + GroovyDsl expectedGroovyDsl = GroovyDsl.make { + request { + method 'POST' + url '/charge/count' + headers { + header('Content-Type').equalTo('application/vnd.creditcard-reporter.v1+json') + } + } + response { + status 200 + body ([ + [a: 1, c: '3'], + 'b', + 'a' + ]) + headers { + header 'Content-Type': 'application/json' + + } + } + } + when: + String groovyDsl = WiremockToDslConverter.fromWiremockStub(wiremockStub) + then: + new GroovyShell(this.class.classLoader).evaluate( + """ io.coderate.accurest.dsl.GroovyDsl.make { + $groovyDsl + }""") == expectedGroovyDsl + } + + + def 'should convert Wiremock stub with body containing a nested list'() { + given: + String wiremockStub = '''\ +{ + "request": { + "method": "POST", + "url": "/charge/search?pageNumber=0&size=2147483647", + "headers": { + "Content-Type": { + "equalTo": "application/vnd.creditcard-reporter.v1+json" + } + } + }, + "response": { + "status": 200, + "body":"[{\\"amount\\":1.01,\\"name\\":\\"Name\\",\\"info\\":{\\"title\\":\\"title1\\",\\"payload\\":null},\\"booleanvalue\\":true,\\"user\\":null},{\\"amount\\":2.01,\\"name\\":\\"Name2\\",\\"info\\":{\\"title\\":\\"title2\\",\\"payload\\":null},\\"booleanvalue\\":true,\\"user\\":null}]" } } +''' + and: + GroovyDsl expectedGroovyDsl = GroovyDsl.make { + request { + method 'POST' + url '/charge/search?pageNumber=0&size=2147483647' + headers { + header('Content-Type').equalTo('application/vnd.creditcard-reporter.v1+json') + } + } + response { + status 200 + body ("""[ + { + "amount": 1.01, + "name": "Name", + "info": { + "title": "title1", + "payload": null + }, + "booleanvalue": true, + "user": null + }, + { + "amount": 2.01, + "name": "Name2", + "info": { + "title": "title2", + "payload": null + }, + "booleanvalue": true, + "user": null + } +]""") + } + } + when: + String groovyDsl = WiremockToDslConverter.fromWiremockStub(wiremockStub) + then: + new GroovyShell(this.class.classLoader).evaluate( + """ io.coderate.accurest.dsl.GroovyDsl.make { + $groovyDsl + }""") == expectedGroovyDsl + } + +} diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/BaseWiremockStubStrategy.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/BaseWiremockStubStrategy.groovy index 3925ada9f3..e02c2a62ce 100644 --- a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/BaseWiremockStubStrategy.groovy +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/BaseWiremockStubStrategy.groovy @@ -12,7 +12,7 @@ abstract class BaseWiremockStubStrategy { } return withAssertionHeaders(headers) { Map.Entry entry -> [(entry.key): buildClientHeaderFromValuePattern(entry.value)] - } << headers?.valueHeaders() + } << headers.valueHeaders() } protected Map buildServerHeadersSection(Headers headers) { diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/GroovyDsl.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/GroovyDsl.groovy index 9ea3348dbd..225d5dce52 100644 --- a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/GroovyDsl.groovy +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/GroovyDsl.groovy @@ -8,7 +8,7 @@ import io.coderate.accurest.dsl.internal.Response @TypeChecked @EqualsAndHashCode(includeFields = true) -@ToString(includePackage = false) +@ToString(includeFields = true, includePackage = false) class GroovyDsl { Request request @@ -32,4 +32,5 @@ class GroovyDsl { closure.delegate = response closure() } + } diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Body.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Body.groovy index e3b75c9eb4..502e18a2ca 100644 --- a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Body.groovy +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Body.groovy @@ -1,14 +1,17 @@ package io.coderate.accurest.dsl.internal -import groovy.transform.CompileStatic + +import groovy.json.JsonSlurper import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +import org.codehaus.groovy.runtime.GStringImpl -@CompileStatic @ToString(includePackage = false, includeFields = true) @EqualsAndHashCode(includeFields = true) class Body { - private final Map body + private Map body + private DslProperty bodyAsValue + private List bodyAsList Body() { this.body = [:] @@ -18,13 +21,51 @@ class Body { this.body = body } - Map forClientSide() { + Body(List bodyAsList) { + this.bodyAsList = bodyAsList + } + + Body(Object bodyAsValue) { + this.bodyAsValue = new DslProperty(bodyAsValue) + } + + Body(GString bodyAsValue) { + this.bodyAsValue = new DslProperty(getClientValue(bodyAsValue), getServerValue(bodyAsValue)) + } + + private Map getClientValue(GString bodyAsValue) { + GString clientGString = new GStringImpl(bodyAsValue.values.clone(), bodyAsValue.strings.clone()) + Object[] clientValues = bodyAsValue.values.collect { it instanceof DslProperty ? it.clientValue : it } as Object[] + return new JsonSlurper().parseText(new GStringImpl(clientValues, clientGString.strings).toString()) + } + + private Map getServerValue(GString bodyAsValue) { + GString clientGString = new GStringImpl(bodyAsValue.values.clone(), bodyAsValue.strings.clone()) + Object[] serverValues = bodyAsValue.values.collect { it instanceof DslProperty ? it.serverValue: it } as Object[] + return new JsonSlurper().parseText(new GStringImpl(serverValues, clientGString.strings).toString()) + } + + Body(DslProperty bodyAsValue) { + this.bodyAsValue = bodyAsValue + } + + Object forClientSide() { + if(bodyAsValue) { + return bodyAsValue.clientValue + } else if(bodyAsList) { + bodyAsList.collect { it.clientValue } + } return body.collectEntries { Map.Entry entry -> [(entry.key) : entry.value.clientValue] } as Map } - Map forServerSide() { + Object forServerSide() { + if(bodyAsValue) { + return bodyAsValue.serverValue + } else if(bodyAsList) { + bodyAsList.collect { it.serverValue } + } return body.collectEntries { Map.Entry entry -> [(entry.key) : entry.value.serverValue] } as Map diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Common.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Common.groovy index fc5135af33..9afb7e115c 100644 --- a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Common.groovy +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Common.groovy @@ -18,10 +18,28 @@ class Common { } as Map } + List convertObjectsToDslProperties(List body) { + return body.collect { + Object element -> toDslProperty(element) + } as List + } + DslProperty toDslProperty(Object property) { return new DslProperty(property) } + DslProperty toDslProperty(Map property) { + return new DslProperty(property.collectEntries { + [(it.key) : toDslProperty(it.value)] + }) + } + + DslProperty toDslProperty(List property) { + return new DslProperty(property.collect { + toDslProperty(it) + }) + } + DslProperty toDslProperty(DslProperty property) { return property } diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Headers.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Headers.groovy index 5487b10b53..f2b5a5649b 100644 --- a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Headers.groovy +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Headers.groovy @@ -4,7 +4,7 @@ import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @EqualsAndHashCode(includeFields = true) -@ToString(includePackage = false) +@ToString(includePackage = false, includeFields = true, ignoreNulls = true) class Headers { private Map assertionHeaders = [:] @@ -28,4 +28,5 @@ class Headers { Set> assertionEntries() { return Collections.unmodifiableSet(assertionHeaders.entrySet()) } + } diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Response.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Response.groovy index 660863c1b0..46827b9875 100644 --- a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Response.groovy +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/internal/Response.groovy @@ -41,6 +41,14 @@ class Response extends Common { this.body = new Body(convertObjectsToDslProperties(body)) } + void body(List body) { + this.body = new Body(convertObjectsToDslProperties(body)) + } + + void body(Object bodyAsValue) { + this.body = new Body(bodyAsValue) + } + Body getBody() { return body } 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 3ee9cd7f11..9740c6f40e 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy @@ -60,6 +60,102 @@ class WiremockGroovyDslSpec extends Specification { ''') } + def 'should convert groovy dsl stub with Body as String to wiremock stub for the client side'() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + method('GET') + urlPattern $(client('/[0-9]{2}'), server('/12')) + } + response { + status 200 + body ("""\ + { + "id": "${value(client('123'),server('321'))}", + "surname": "${value(client('Kowalsky'),server('Lewandowski'))}", + "name": "Jan", + "created" : "${$(client('2014-02-02 12:23:43'), server('2999-09-09 01:23:45'))}" + } + """ + ) + headers { + header 'Content-Type': 'text/plain' + } + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockClientStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' +{ + "request": { + "method": "GET", + "urlPattern": "/[0-9]{2}" + }, + "response": { + "status": 200, + "body": { + "id": "123", + "surname": "Kowalsky", + "name": "Jan", + "created" : "2014-02-02 12:23:43" + }, + "headers": { + "Content-Type": "text/plain" + } + } +} +''') + } + + def 'should convert groovy dsl stub with Body as String to wiremock stub for the server side'() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + method('GET') + urlPattern $(client('/[0-9]{2}'), server('/12')) + } + response { + status 200 + body ("""\ + { + "id": "${value(client('123'),server('321'))}", + "surname": "${value(client('Kowalsky'),server('Lewandowski'))}", + "name": "Jan", + "created" : "${$(client('2014-02-02 12:23:43'), server('2999-09-09 01:23:45'))}" + } + """ + ) + headers { + header 'Content-Type': 'text/plain' + } + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockServerStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' +{ + "request": { + "method": "GET", + "urlPattern": "/12" + }, + "response": { + "status": 200, + "body": { + "id": "321", + "surname": "Lewandowski", + "name": "Jan", + "created" : "2999-09-09 01:23:45" + }, + "headers": { + "Content-Type": "text/plain" + } + } +} +''') + } + def 'should convert groovy dsl stub to wiremock stub for the server side'() { given: GroovyDsl groovyDsl = GroovyDsl.make { diff --git a/build.gradle b/build.gradle index a74ec2a59f..8a123cd670 100644 --- a/build.gradle +++ b/build.gradle @@ -47,6 +47,7 @@ project(':accurest-core') { project(':accurest-converters') { dependencies { compile project(':accurest-core') + compile 'org.apache.commons:commons-lang3:3.3.2' } }