diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/AccurestException.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/AccurestException.groovy new file mode 100644 index 0000000000..d564661933 --- /dev/null +++ b/accurest-core/src/main/groovy/io/coderate/accurest/AccurestException.groovy @@ -0,0 +1,12 @@ +package io.coderate.accurest + +/** + * @author Jakub Kubrynski + */ +class AccurestException extends RuntimeException { + + def AccurestException(String message) { + super(message) + } + +} diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/TestGenerator.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/TestGenerator.groovy index 7257913ec0..2042f25461 100644 --- a/accurest-core/src/main/groovy/io/coderate/accurest/TestGenerator.groovy +++ b/accurest-core/src/main/groovy/io/coderate/accurest/TestGenerator.groovy @@ -32,7 +32,7 @@ class TestGenerator { this.targetDirectory = accurestConfigProperties.generatedTestSourcesDir File stubsResource = new File(accurestConfigProperties.stubsBaseDirectory) if (stubsResource == null) { - throw new IllegalStateException("Stubs directory not found under " + accurestConfigProperties.stubsBaseDirectory) + throw new AccurestException("Stubs directory not found under " + accurestConfigProperties.stubsBaseDirectory) } this.stubsBaseDirectory = stubsResource.path } 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 new file mode 100644 index 0000000000..35a2acbc7d --- /dev/null +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/GroovyDsl.groovy @@ -0,0 +1,23 @@ +package io.coderate.accurest.dsl + +import groovy.transform.TypeChecked + +@TypeChecked +class GroovyDsl { + + Request request + + static GroovyDsl make(Closure closure) { + GroovyDsl dsl = new GroovyDsl() + closure.delegate = dsl + closure() + return dsl + } + + void request(@DelegatesTo(Request) Closure closure) { + Request request = new Request() + this.request = request + closure.delegate = request + closure() + } +} diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/Request.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/Request.groovy new file mode 100644 index 0000000000..d4f40ef6ba --- /dev/null +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/Request.groovy @@ -0,0 +1,172 @@ +package io.coderate.accurest.dsl + +import groovy.transform.TypeChecked + +@TypeChecked +class Request { + String method + String url + StringCustomizableProperty urlPattern + String urlPath + Headers headers + + void method(String method) { + this.method = method + } + + void url(String url) { + this.url = url + } + + void urlPattern(@DelegatesTo(StringCustomizableProperty) Closure closure) { + StringCustomizableProperty urlPattern = new StringCustomizableProperty() + this.urlPattern = urlPattern + delegateToClosure(closure, urlPattern) + } + + void headers(@DelegatesTo(Headers) Closure closure) { + Headers headers = new Headers() + this.headers = headers + delegateToClosure(closure, headers) + } + + void urlPath(String urlPath) { + this.urlPath = urlPath + } + + //TODO: Can we have different types for Client/Server (client: pattern(String), server: value(Boolean/Int)) ? + class CustomizableProperty { + private T client + private V server + + void client(T client) { + this.client = client + } + + void server(V server) { + this.server = server + } + + T toClientSide() { + return client + } + + V toServerSide() { + return server + } + } + + private class StringCustomizableProperty extends CustomizableProperty { + @Override + String toClientSide() { + return super.toClientSide() as String + } + + @Override + String toServerSide() { + return super.toServerSide() as String + } + } + + private class NoOpCustomizableProperty extends CustomizableProperty { + NoOpCustomizableProperty(T value) { + client(value) + server(value) + } + + @Override + public String toString() { + return toClientSide() + } + } + + class Headers { + private Map headers = [:] + + WithValuePattern header(String headerName) { + WithValuePattern withValuePattern = new WithValuePattern() + headers[headerName] = withValuePattern + return withValuePattern + } + + Set> entries() { + return headers.entrySet() + } + } + +/** + * + * From Wiremock + private Map queryParamPatterns; + private List bodyPatterns; + + private String equalToJson; + private String equalToXml; + private String matchesXPath; + private JSONCompareMode jsonCompareMode; + private String equalTo; + private String contains; + private String matches; + private String doesNotMatch; + private Boolean absent; + private String matchesJsonPath; + + */ + class WithValuePattern { + NoOpCustomizableProperty equalToJson + NoOpCustomizableProperty equalToXml + StringCustomizableProperty matchesXPath + NoOpCustomizableProperty jsonCompareMode + NoOpCustomizableProperty equalTo + StringCustomizableProperty contains + StringCustomizableProperty matches + StringCustomizableProperty doesNotMatch + CustomizableProperty absent + StringCustomizableProperty matchesJsonPath + +// void equalToJson(String equalToJson) { +// this.equalToJson = equalToJson +// } +// + void equalTo(String equalTo) { + this.equalTo = new NoOpCustomizableProperty(equalTo) + } + +// void equalToXml(String equalToXml) { +// this.equalToXml = equalToXml +// } + + void matches(@DelegatesTo(StringCustomizableProperty) Closure closure) { + StringCustomizableProperty placeholderHaving = new StringCustomizableProperty() + this.matches = placeholderHaving + delegateToClosure(closure, placeholderHaving) + } + + void doesNotMatch(@DelegatesTo(StringCustomizableProperty) Closure closure) { + StringCustomizableProperty placeholderHaving = new StringCustomizableProperty() + this.doesNotMatch = placeholderHaving + delegateToClosure(closure, placeholderHaving) + } + + void contains(@DelegatesTo(StringCustomizableProperty) Closure closure) { + StringCustomizableProperty placeholderHaving = new StringCustomizableProperty() + this.contains = placeholderHaving + delegateToClosure(closure, placeholderHaving) + } + +// void jsonCompareMode(JSONCompareMode jsonCompareMode) { +// this.jsonCompareMode = jsonCompareMode +// } + + } + + enum JSONCompareMode { + STRICT, LENIENT, NON_EXTENSIBLE, STRICT_ORDER; + } + + private static void delegateToClosure(@DelegatesTo(T) Closure closure, T delegate) { + closure.delegate = delegate + closure() + } + +} \ No newline at end of file diff --git a/accurest-core/src/main/groovy/io/coderate/accurest/dsl/WiremockStubStrategy.groovy b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/WiremockStubStrategy.groovy new file mode 100644 index 0000000000..ea975a0ca8 --- /dev/null +++ b/accurest-core/src/main/groovy/io/coderate/accurest/dsl/WiremockStubStrategy.groovy @@ -0,0 +1,86 @@ +package io.coderate.accurest.dsl + +import groovy.json.JsonOutput +import groovy.transform.CompileStatic + +@CompileStatic +class WiremockStubStrategy { + + private final Request request + + WiremockStubStrategy(GroovyDsl groovyDsl) { + this.request = groovyDsl.request + } + + String toWiremockClientStub() { + return JsonOutput.toJson(buildClientRequest(request)) + + } + + String toWiremockServerStub() { + return JsonOutput.toJson(buildServerRequest(request)) + } + + private Map buildClientRequest(Request request) { + return getRequestSection(request, + { request.urlPattern?.toClientSide() }, + { buildClientHeadersSection(request.headers) }) + } + + private Map buildServerRequest(Request request) { + return getRequestSection(request, + { request.urlPattern?.toServerSide() }, + { buildServerHeadersSection(request.headers) }) + } + + private Map> getRequestSection(Request request, Closure buildUrlPattern, Closure buildHeaders) { + return [request: [method : request.method, + url : request.url, + urlPattern: buildUrlPattern(), + urlPath : request.urlPath, + headers : buildHeaders()].findAll { it.value }] + } + + private Map buildClientHeadersSection(Request.Headers headers) { + return createHeadersSection(headers) { + Map.Entry entry -> [(entry.key): buildClientHeaderFromValuePattern(entry.value)] + } + } + + private Map buildServerHeadersSection(Request.Headers headers) { + return createHeadersSection(headers) { + Map.Entry entry -> [(entry.key): buildServerHeaderFromValuePattern(entry.value)] + } + } + + private Map createHeadersSection(Request.Headers headers, Closure closure) { + return headers?.entries()?.collectEntries(closure) + } + + + private Map buildClientHeaderFromValuePattern(Request.WithValuePattern valuePattern) { + return getValuePatternSection(valuePattern) + .findAll { it.value } + .collectEntries { [(it.key): it.value.toClientSide()] } + } + + private Map buildServerHeaderFromValuePattern(Request.WithValuePattern valuePattern) { + return getValuePatternSection(valuePattern) + .findAll { it.value } + .collectEntries { [(it.key): it.value.toServerSide()] } + } + + private Map getValuePatternSection(Request.WithValuePattern valuePattern) { + return [equalToJson : valuePattern.equalToJson, + equalToXml : valuePattern.equalToXml, + matchesXPath : valuePattern.matchesXPath, + jsonCompareMode: valuePattern.jsonCompareMode, + equalTo : valuePattern.equalTo, + contains : valuePattern.contains, + matches : valuePattern.matches, + doesNotMatch : valuePattern.doesNotMatch, + absent : valuePattern.absent, + matchesJsonPath: valuePattern.matchesJsonPath] + } + +} 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 new file mode 100644 index 0000000000..f6feb8710e --- /dev/null +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy @@ -0,0 +1,270 @@ +package io.codearte.accurest.dsl + +import groovy.json.JsonSlurper +import io.coderate.accurest.dsl.GroovyDsl +import io.coderate.accurest.dsl.WiremockStubStrategy +import spock.lang.Ignore +import spock.lang.Specification + +class WiremockGroovyDslSpec extends Specification { + + // TODO: add alias instead of placeholder + @Ignore + def 'should convert groovy dsl stub to wiremock stub'() { + given: + GroovyDsl dsl = GroovyDsl.make { + request { + method('GET') + urlPattern { + client('/[0-9]{2}') + server('/12') + } + } + response { + status(200) + body { + withPlaceholder(client: '2015-01-14', server: '$anyInt($it)') + withTemplate(''' + { + "date" : "$placeholder0" + } + ''') + } + headers { + Content-Type('text/plain') + } + } + } + expect: + dsl.toWiremockClientStub() == ''' +{ + "request": { + "method": "GET", + "urlPattern": "/[0-9]{2}" + }, + "response": { + "status": 200, + "body": "2015-01-14", + "headers": { + "Content-Type": "text/plain" + } + } +} +''' + } + + def "should generate stub with GET"() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + method("GET") + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockClientStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' + { + "request":{ + "method":"GET" + } + } + ''') + } + + def "should generate request when two elements are provided "() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + method("GET") + url("/sth") + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockClientStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' + { + "request":{ + "method":"GET", + "url":"/sth" + } + } + ''') + } + + def "should generate request with urlPattern for client side"() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + urlPattern { + client('/^[0-9]{2}$') + } + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockClientStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' + { + "request":{ + "urlPattern":"/^[0-9]{2}$" + } + } + ''') + } + + def "should generate stub with urlPattern for server side"() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + urlPattern { + client('/^[0-9]{2}$') + server('/12') + } + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockServerStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' + { + "request":{ + "urlPattern":"/12" + } + } + ''') + } + + def "should generate stub with urlPath for client side"() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + urlPath ('/12') + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockClientStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' + { + "request":{ + "urlPath":"/12" + } + } + ''') + } + + def "should generate stub with urlPath for server side"() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + urlPath ('/12') + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockServerStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' + { + "request":{ + "urlPath":"/12" + } + } + ''') + } + + def "should generate stub with some headers section for client side"() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + headers { + header('Content-Type').equalTo('text/xml') + header('Accept').matches { + client('text/.*') + server('text/plain') + } + header('etag').doesNotMatch { + client('abcd.*') + server('abcdef') + } + header('X-Custom-Header').contains { + client('2134') + server('121345') + } + } + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockClientStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' + { + "request":{ + "headers": { + "Content-Type": { + "equalTo": "text/xml" + }, + "Accept": { + "matches": "text/.*" + }, + "etag": { + "doesNotMatch": "abcd.*" + }, + "X-Custom-Header": { + "contains": "2134" + } + } + } + } + ''') + } + + def "should generate stub with some headers section for server side"() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + headers { + header('Content-Type').equalTo('text/xml') + header('Accept').matches { + client('text/.*') + server('text/plain') + } + header('etag').doesNotMatch { + client('abcd.*') + server('abcdef') + } + header('X-Custom-Header').contains { + client('2134') + server('121345') + } + } + } + } + when: + String wiremockStub = new WiremockStubStrategy(groovyDsl).toWiremockServerStub() + then: + new JsonSlurper().parseText(wiremockStub) == new JsonSlurper().parseText(''' + { + "request":{ + "headers": { + "Content-Type": { + "equalTo": "text/xml" + }, + "Accept": { + "matches": "text/plain" + }, + "etag": { + "doesNotMatch": "abcdef" + }, + "X-Custom-Header": { + "contains": "121345" + } + } + } + } + ''') + } + + +} diff --git a/accurest-gradle-plugin/src/main/groovy/io/codearte/accurest/plugin/AccurestGradlePlugin.groovy b/accurest-gradle-plugin/src/main/groovy/io/codearte/accurest/plugin/AccurestGradlePlugin.groovy index c86884d0b8..b3bd8d6509 100644 --- a/accurest-gradle-plugin/src/main/groovy/io/codearte/accurest/plugin/AccurestGradlePlugin.groovy +++ b/accurest-gradle-plugin/src/main/groovy/io/codearte/accurest/plugin/AccurestGradlePlugin.groovy @@ -1,5 +1,6 @@ package io.codearte.accurest.plugin +import io.coderate.accurest.AccurestException import io.coderate.accurest.TestGenerator import io.coderate.accurest.config.AccurestConfigProperties import org.gradle.api.Plugin @@ -24,19 +25,20 @@ class AccurestGradlePlugin implements Plugin { project.logger.info("Accurest Plugin: Invoking test sources generation") extension.stubsBaseDirectory = project.projectDir.path + File.separator + extension.stubsBaseDirectory - extension.generatedTestSourcesDir = buildGeneratedSourcesDir(project, extension) project.sourceSets.test.groovy { project.logger.info("Registering $extension.generatedTestSourcesDir as test source directory") srcDir extension.generatedTestSourcesDir } + extension.generatedTestSourcesDir = buildGeneratedSourcesDir(project, extension) + try { TestGenerator generator = new TestGenerator(extension) int generatedClasses = generator.generate() project.logger.info("Generated {} test classes", generatedClasses) - } catch (IllegalStateException e) { - project.logger.error("Accurest Plugin: {}", e.getMessage()) + } catch (AccurestException e) { + project.logger.error("Accurest Plugin exception: {}", e.getMessage()) } } diff --git a/build.gradle b/build.gradle index 701ea81137..33ecf9a2db 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,9 @@ subprojects { dependencies { compile localGroovy() - testCompile 'org.spockframework:spock-core:0.7-groovy-2.0' + testCompile('org.spockframework:spock-core:0.7-groovy-2.0') { + exclude(group: 'org.codehaus.groovy') + } } publishing {