From b0bb4cb75fdbd12de4237a12361d23aea1e3c239 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 5 Oct 2015 22:38:12 +0200 Subject: [PATCH] [#42] Modified the optional functionality --- .../accurest/dsl/internal/Common.groovy | 18 +- .../accurest/dsl/internal/Optional.groovy | 7 - .../dsl/internal/OptionalProperty.groovy | 13 ++ .../accurest/dsl/internal/Request.groovy | 5 +- .../accurest/dsl/internal/Response.groovy | 3 + .../accurest/util/ContentUtils.groovy | 28 +-- .../util/JsonToJsonPathsConverter.groovy | 14 +- .../accurest/util/MapConverter.groovy | 5 - .../MockMvcSpockMethodBuilderSpec.groovy | 119 +++++------ .../accurest/dsl/WireMockGroovyDslSpec.groovy | 190 ++++++++---------- .../accurest/dsl/WireMockStubVerifier.groovy | 2 +- 11 files changed, 190 insertions(+), 214 deletions(-) delete mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Optional.groovy create mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/OptionalProperty.groovy diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Common.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Common.groovy index 531dbec5ce..c96ff4b2d4 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Common.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Common.groovy @@ -16,20 +16,16 @@ class Common { @Delegate private final RegexPatterns regexPatterns = new RegexPatterns() Map convertObjectsToDslProperties(Map body) { - return (body.collectEntries { + return body.collectEntries { Map.Entry entry -> [(entry.key): toDslProperty(entry.value)] - } as Map).findAll { - !(it.value.clientValue instanceof Optional || it.value.serverValue instanceof Optional) - } + } as Map } Collection convertObjectsToDslProperties(List body) { return (body.collect { Object element -> toDslProperty(element) - } as List).findAll { - !(it instanceof Optional) - } + } as List) } DslProperty toDslProperty(Object property) { @@ -78,6 +74,10 @@ class Common { return Pattern.compile(regex) } + OptionalProperty optional(Object object) { + return new OptionalProperty(object) + } + ExecutionProperty execute(String commandToExecute) { return new ExecutionProperty(commandToExecute) } @@ -98,8 +98,8 @@ class Common { return new ServerDslProperty(serverValue) } - Optional optional() { - return new Optional() + void assertThatSidesMatch(OptionalProperty stubSide, Object testSide) { + assert testSide ==~ Pattern.compile(stubSide.optionalPattern()) } void assertThatSidesMatch(Pattern pattern, String value) { diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Optional.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Optional.groovy deleted file mode 100644 index 02e7a16323..0000000000 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Optional.groovy +++ /dev/null @@ -1,7 +0,0 @@ -package io.codearte.accurest.dsl.internal - -/** - * Marker class to show that an element of message is optional - */ -class Optional { -} diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/OptionalProperty.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/OptionalProperty.groovy new file mode 100644 index 0000000000..877c31c661 --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/OptionalProperty.groovy @@ -0,0 +1,13 @@ +package io.codearte.accurest.dsl.internal + +class OptionalProperty { + final Object value + + OptionalProperty(Object value) { + this.value = value + } + + String optionalPattern() { + return "($value)?" + } +} 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 9be791488a..9adf7048a8 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 @@ -3,7 +3,6 @@ import groovy.transform.CompileStatic import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import groovy.transform.TypeChecked -import groovy.xml.MarkupBuilder @TypeChecked @EqualsAndHashCode @@ -129,6 +128,10 @@ class Request extends Common { return new MatchingStrategy(true, MatchingStrategy.Type.ABSENT) } + void assertThatSidesMatch(Object stubSide, OptionalProperty testSide) { + throw new IllegalStateException("Optional can be used only for the stub side of the request!") + } + } @CompileStatic diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Response.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Response.groovy index 3715f0c243..b2546cd5c8 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Response.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/internal/Response.groovy @@ -49,6 +49,9 @@ class Response extends Common { this.body = new Body(bodyAsValue) } + void assertThatSidesMatch(OptionalProperty stubSide, Object testSide) { + throw new IllegalStateException("Optional can be used only in the test side of the response!") + } } @CompileStatic diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/ContentUtils.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/util/ContentUtils.groovy index c5261b43be..1b370b7126 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/util/ContentUtils.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/ContentUtils.groovy @@ -8,7 +8,7 @@ import io.codearte.accurest.dsl.internal.DslProperty import io.codearte.accurest.dsl.internal.ExecutionProperty import io.codearte.accurest.dsl.internal.Headers import io.codearte.accurest.dsl.internal.MatchingStrategy -import io.codearte.accurest.dsl.internal.Optional +import io.codearte.accurest.dsl.internal.OptionalProperty import org.codehaus.groovy.runtime.GStringImpl import java.util.regex.Matcher @@ -27,9 +27,9 @@ class ContentUtils { private static final Pattern TEMPORARY_PATTERN_HOLDER = Pattern.compile('.*REGEXP>>(.*)<<.*') private static final Pattern TEMPORARY_EXECUTION_PATTERN_HOLDER = Pattern.compile('EXECUTION>>(.*)<<') + private static final Pattern TEMPORARY_OPTIONAL_PATTERN_HOLDER = Pattern.compile('OPTIONAL>>(.*)<<') private static final String JSON_VALUE_PATTERN_FOR_REGEX = 'REGEXP>>%s<<' - private static final String JSON_VALUE_OPTIONAL = 'OPTIONAL>><<' - private static final Pattern OPTIONAL_PATTERN_HOLDER = Pattern.compile(JSON_VALUE_OPTIONAL) + private static final String JSON_VALUE_PATTERN_FOR_OPTIONAL = 'OPTIONAL>>%s<<' private static final String JSON_VALUE_PATTERN_FOR_EXECUTION = '"EXECUTION>>%s<<"' /** @@ -160,8 +160,8 @@ class ContentUtils { return String.format(JSON_VALUE_PATTERN_FOR_REGEX, pattern.pattern()) } - private static String transformJSONStringValue(Optional optional, Closure valueProvider) { - return JSON_VALUE_OPTIONAL + private static String transformJSONStringValue(OptionalProperty optional, Closure valueProvider) { + return String.format(JSON_VALUE_PATTERN_FOR_OPTIONAL, optional.value) } private static String transformJSONStringValue(ExecutionProperty property, Closure valueProvider) { @@ -213,23 +213,25 @@ class ContentUtils { static Object returnParsedObject(String string) { Matcher matcher = TEMPORARY_PATTERN_HOLDER.matcher(string.trim()) if (matcher.matches()) { - List val = matcher[0] as List - String pattern = val[1] - return Pattern.compile(pattern) + return Pattern.compile(patternFromMatchingGroup(matcher)) } Matcher executionMatcher = TEMPORARY_EXECUTION_PATTERN_HOLDER.matcher(string.trim()) if (executionMatcher.matches()) { - List val = executionMatcher[0] as List - String pattern = val[1] - return new ExecutionProperty(pattern) + return new ExecutionProperty(patternFromMatchingGroup(executionMatcher)) } - Matcher optionalMatcher = OPTIONAL_PATTERN_HOLDER.matcher(string.trim()) + Matcher optionalMatcher = TEMPORARY_OPTIONAL_PATTERN_HOLDER.matcher(string.trim()) if (optionalMatcher.matches()) { - return new Optional() + String patternToMatch = patternFromMatchingGroup(optionalMatcher) + return Pattern.compile(new OptionalProperty(patternToMatch).optionalPattern()) } return string } + private static String patternFromMatchingGroup(Matcher matcher) { + List val = matcher[0] as List + return val[1] + } + public static ContentType recognizeContentTypeFromHeader(Headers headers) { String content = headers?.entries.find { it.name == "Content-Type" } ?.clientValue?.toString() if (content?.endsWith("json")) { diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/JsonToJsonPathsConverter.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/util/JsonToJsonPathsConverter.groovy index db8f264e30..319e7ab660 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/util/JsonToJsonPathsConverter.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/JsonToJsonPathsConverter.groovy @@ -1,9 +1,9 @@ package io.codearte.accurest.util - +import java.util.regex.Pattern import groovy.json.JsonSlurper import io.codearte.accurest.dsl.internal.ExecutionProperty +import io.codearte.accurest.dsl.internal.OptionalProperty -import java.util.regex.Pattern /** * @author Marcin Grzejszczak */ @@ -30,7 +30,7 @@ class JsonToJsonPathsConverter { JsonPaths pathsAndValues = [] as Set Object convertedJson = MapConverter.getClientOrServerSideValues(json, clientSide) traverseRecursivelyForKey(convertedJson, ROOT_JSON_PATH_ELEMENT) { String key, Object value -> - if (value instanceof ExecutionProperty || value instanceof io.codearte.accurest.dsl.internal.Optional) { + if (value instanceof ExecutionProperty) { return } JsonPathEntry entry = getValueToInsert(key, value) @@ -128,13 +128,19 @@ class JsonToJsonPathsConverter { protected static String compareWith(Object value) { if (value instanceof Pattern) { - return """=~ /${(value as Pattern).pattern()}/""" + return patternComparison((value as Pattern).pattern()) + } else if (value instanceof OptionalProperty) { + return patternComparison((value as OptionalProperty).optionalPattern()) } else if (value instanceof GString) { return """=~ /${RegexpBuilders.buildGStringRegexpForTestSide(value)}/""" } return """== ${potentiallyWrappedWithQuotesValue(value)}""" } + protected static String patternComparison(String pattern){ + return """=~ /$pattern/""" + } + protected static String potentiallyWrappedWithQuotesValue(Object value) { return value instanceof Number ? value : "'$value'" } diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/MapConverter.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/util/MapConverter.groovy index c4a6eed44b..22896d8308 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/util/MapConverter.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/MapConverter.groovy @@ -1,9 +1,6 @@ package io.codearte.accurest.util - import groovy.json.JsonSlurper import io.codearte.accurest.dsl.internal.DslProperty -import io.codearte.accurest.dsl.internal.Optional - /** * @author Marcin Grzejszczak */ @@ -41,8 +38,6 @@ class MapConverter { return map.collectEntries { key, value -> [key, transformValues(value, closure)] - }.findAll { - !(it.value instanceof Optional) } } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcSpockMethodBuilderSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcSpockMethodBuilderSpec.groovy index f3ee4bd99a..44cb4e235f 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcSpockMethodBuilderSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcSpockMethodBuilderSpec.groovy @@ -5,6 +5,7 @@ import io.codearte.accurest.dsl.WireMockStubStrategy import io.codearte.accurest.dsl.WireMockStubVerifier import spock.lang.Issue import spock.lang.Specification +import spock.lang.Unroll /** * @author Jakub Kubrynski @@ -553,89 +554,77 @@ class MockMvcSpockMethodBuilderSpec extends Specification implements WireMockStu } @Issue('42') - def "should omit an optional field from body resolution"() { + @Unroll + def "should not omit the optional field in the test creation"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - priority 1 - request { - method 'POST' - url '/users/password' - headers { - header 'Content-Type': 'application/json' - } - body( - email: optional(), - callback_url: $(client(regex(hostname())), server('http://partners.com')) - ) - } - response { - status 404 - headers { - header 'Content-Type': 'application/json' - } - body( - code: optional(), - message: "User not found by email = [${value(server(regex(email())), client('not.existing@user.com'))}]" - ) - } - } MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl) BlockBuilder blockBuilder = new BlockBuilder(" ") when: builder.appendTo(blockBuilder) def spockTest = blockBuilder.toString() then: - !spockTest.contains('''body('{"email":''') - !spockTest.contains('''parsedJson.read(\'\'\'$[?(@.email''') + spockTest.contains('''"email":"abc@abc.com"''') + spockTest.contains('''parsedJson.read(\'\'\'$[?(@.code =~ /(123123)?/)]''') !spockTest.contains('''REGEXP''') !spockTest.contains('''OPTIONAL''') - !spockTest.contains('''Optional''') - } - - @Issue('42') - def "should omit an optional field from body resolution with GString"() { - given: - GroovyDsl contractDsl = GroovyDsl.make { - priority 1 - request { - method 'POST' - url '/users/password' - headers { - header 'Content-Type': 'application/json' + !spockTest.contains('''OptionalProperty''') + where: + contractDsl << [ + GroovyDsl.make { + priority 1 + request { + method 'POST' + url '/users/password' + headers { + header 'Content-Type': 'application/json' + } + body( + email: $(stub(optional(regex(email()))), test('abc@abc.com')), + callback_url: $(stub(regex(hostname())), test('http://partners.com')) + ) } - body( - """ { - "email" : "${value(optional())}", + response { + status 404 + headers { + header 'Content-Type': 'application/json' + } + body( + code: value(stub("123123"), test(optional("123123"))), + message: "User not found by email = [${value(test(regex(email())), stub('not.existing@user.com'))}]" + ) + } + }, + GroovyDsl.make { + priority 1 + request { + method 'POST' + url '/users/password' + headers { + header 'Content-Type': 'application/json' + } + body( + """ { + "email" : "${value(stub(optional(regex(email()))), test('abc@abc.com'))}", "callback_url" : "${value(client(regex(hostname())), server('http://partners.com'))}" } """ - ) - } - response { - status 404 - headers { - header 'Content-Type': 'application/json' + ) } - body( - """ { - "code" : "${value(optional())}", + response { + status 404 + headers { + header 'Content-Type': 'application/json' + } + body( + """ { + "code" : "${value(stub(123123), test(optional(123123)))}", "message" : "User not found by email = [${value(server(regex(email())), client('not.existing@user.com'))}]" } """ - ) + ) + } } - } - MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") - when: - builder.appendTo(blockBuilder) - def spockTest = blockBuilder.toString() - then: - !spockTest.contains('''body('{"email":''') - !spockTest.contains('''parsedJson.read(\'\'\'$[?(@.code''') - !spockTest.contains('''REGEXP''') - !spockTest.contains('''OPTIONAL''') - !spockTest.contains('''Optional''') + ] } @Issue('72') 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 cbceb49f80..7502ada771 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 @@ -5,6 +5,7 @@ import groovy.json.JsonSlurper import io.codearte.accurest.util.AssertionUtil import spock.lang.Issue import spock.lang.Specification +import spock.lang.Unroll class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifier { @@ -1294,125 +1295,96 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie } @Issue('42') - def 'should generate stub without optional parameters with GString'() { - given: - GroovyDsl groovyDsl = GroovyDsl.make { - priority 1 - request { - method 'POST' - url '/users/password' - headers { - header 'Content-Type': 'application/json' + @Unroll + def 'should generate stub without optional parameters'() { + when: + String wireMockStub = new WireMockStubStrategy(contractDsl).toWireMockClientStub() + then: + AssertionUtil.assertThatJsonsAreEqual((''' + { + "request" : { + "url" : "/users/password", + "method" : "POST", + "bodyPatterns" : [ { + "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]" + }, { + "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})?/)]" + } ], + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + } + }, + "response" : { + "status" : 404, + "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email = [not.existing@user.com]\\"}", + "headers" : { + "Content-Type" : "application/json" + } + }, + "priority" : 1 + } + '''), wireMockStub) + and: + stubMappingIsValidWireMockStub(wireMockStub) + where: + contractDsl << [ + GroovyDsl.make { + priority 1 + request { + method 'POST' + url '/users/password' + headers { + header 'Content-Type': 'application/json' + } + body( + email: $(stub(optional(regex(email()))), test('abc@abc.com')), + callback_url: $(stub(regex(hostname())), test('http://partners.com')) + ) } - body( - """ { - "email" : "${value(optional())}", + response { + status 404 + headers { + header 'Content-Type': 'application/json' + } + body( + code: $(stub("123123"), test(optional("123123"))), + message: "User not found by email = [${value(test(regex(email())), stub('not.existing@user.com'))}]" + ) + } + }, + GroovyDsl.make { + priority 1 + request { + method 'POST' + url '/users/password' + headers { + header 'Content-Type': 'application/json' + } + body( + """ { + "email" : "${value(stub(optional(regex(email()))), test('abc@abc.com'))}", "callback_url" : "${value(client(regex(hostname())), server('http://partners.com'))}" } """ - ) - } - response { - status 404 - headers { - header 'Content-Type': 'application/json' + ) } - body( - """ { - "code" : "${value(optional())}", + response { + status 404 + headers { + header 'Content-Type': 'application/json' + } + body( + """ { + "code" : "${value(stub(123123), test(optional(123123)))}", "message" : "User not found by email = [${value(server(regex(email())), client('not.existing@user.com'))}]" } """ - ) - } - } - when: - String wireMockStub = new WireMockStubStrategy(groovyDsl).toWireMockClientStub() - then: - AssertionUtil.assertThatJsonsAreEqual((''' - { - "request" : { - "url" : "/users/password", - "method" : "POST", - "bodyPatterns" : [ { - "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]" - } ], - "headers" : { - "Content-Type" : { - "equalTo" : "application/json" - } - } - }, - "response" : { - "status" : 404, - "body" : "{\\"message\\":\\"User not found by email = [not.existing@user.com]\\"}", - "headers" : { - "Content-Type" : "application/json" - } - }, - "priority" : 1 - } - '''), wireMockStub) - and: - stubMappingIsValidWireMockStub(wireMockStub) - } - - @Issue('42') - def 'should generate stub without optional parameters'() { - given: - GroovyDsl groovyDsl = GroovyDsl.make { - priority 1 - request { - method 'POST' - url '/users/password' - headers { - header 'Content-Type': 'application/json' + ) } - body( - email: optional(), - callback_url: $(client(regex(hostname())), server('http://partners.com')) - ) } - response { - status 404 - headers { - header 'Content-Type': 'application/json' - } - body( - code: optional(), - message: "User not found by email = [${value(server(regex(email())), client('not.existing@user.com'))}]" - ) - } - } - when: - String wireMockStub = new WireMockStubStrategy(groovyDsl).toWireMockClientStub() - then: - AssertionUtil.assertThatJsonsAreEqual((''' - { - "request" : { - "url" : "/users/password", - "method" : "POST", - "bodyPatterns" : [ { - "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]" - } ], - "headers" : { - "Content-Type" : { - "equalTo" : "application/json" - } - } - }, - "response" : { - "status" : 404, - "body" : "{\\"message\\":\\"User not found by email = [not.existing@user.com]\\"}", - "headers" : { - "Content-Type" : "application/json" - } - }, - "priority" : 1 - } - '''), wireMockStub) - and: - stubMappingIsValidWireMockStub(wireMockStub) + ] } String toJsonString(value) { diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WireMockStubVerifier.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WireMockStubVerifier.groovy index 241380062f..537fb78ea2 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WireMockStubVerifier.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WireMockStubVerifier.groovy @@ -10,7 +10,7 @@ trait WireMockStubVerifier { stubMapping.request.bodyPatterns.findAll { it.matches }.every { Pattern.compile(it.matches) } - assert !mappingDefinition.contains('DslProperty') + assert !mappingDefinition.contains('io.codearte.accurest.dsl.internal') } }