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 6cb78b67ba..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 @@ -22,10 +22,10 @@ class Common { } as Map } - List convertObjectsToDslProperties(List body) { - return body.collect { + Collection convertObjectsToDslProperties(List body) { + return (body.collect { Object element -> toDslProperty(element) - } as List + } as List) } DslProperty toDslProperty(Object property) { @@ -53,6 +53,10 @@ class Common { return new DslProperty(client.clientValue, server.serverValue) } + DslProperty value(Object value) { + return new DslProperty(value) + } + DslProperty value(ServerDslProperty server, ClientDslProperty client) { assertThatSidesMatch(client.clientValue, server.serverValue) return new DslProperty(client.clientValue, server.serverValue) @@ -70,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) } @@ -90,6 +98,10 @@ class Common { return new ServerDslProperty(serverValue) } + void assertThatSidesMatch(OptionalProperty stubSide, Object testSide) { + assert testSide ==~ Pattern.compile(stubSide.optionalPattern()) + } + void assertThatSidesMatch(Pattern pattern, String value) { assert value ==~ pattern } 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 c18a918c8b..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,6 +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.OptionalProperty import org.codehaus.groovy.runtime.GStringImpl import java.util.regex.Matcher @@ -24,9 +25,11 @@ class ContentUtils { it instanceof DslProperty ? it.clientValue : it } - private static final Pattern TEMPORARY_PATTERN_HOLDER = Pattern.compile('REGEXP>>(.*)<<') + 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_PATTERN_FOR_OPTIONAL = 'OPTIONAL>>%s<<' private static final String JSON_VALUE_PATTERN_FOR_EXECUTION = '"EXECUTION>>%s<<"' /** @@ -157,6 +160,10 @@ class ContentUtils { return String.format(JSON_VALUE_PATTERN_FOR_REGEX, pattern.pattern()) } + 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) { return String.format(JSON_VALUE_PATTERN_FOR_EXECUTION, property.executionCommand) } @@ -206,19 +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 = TEMPORARY_OPTIONAL_PATTERN_HOLDER.matcher(string.trim()) + if (optionalMatcher.matches()) { + 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 32affecef2..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 */ @@ -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 ca0717ba68..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,8 +1,6 @@ package io.codearte.accurest.util - import groovy.json.JsonSlurper import io.codearte.accurest.dsl.internal.DslProperty - /** * @author Marcin Grzejszczak */ 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 7c16b662d7..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 @@ -552,6 +553,80 @@ class MockMvcSpockMethodBuilderSpec extends Specification implements WireMockStu spockTest.contains('''$[?(@.message =~ /User not found by email = \\\\[[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4}\\\\]/)]''') } + @Issue('42') + @Unroll + def "should not omit the optional field in the test creation"() { + given: + MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + def spockTest = blockBuilder.toString() + then: + spockTest.contains('''"email":"abc@abc.com"''') + spockTest.contains('''parsedJson.read(\'\'\'$[?(@.code =~ /(123123)?/)]''') + !spockTest.contains('''REGEXP''') + !spockTest.contains('''OPTIONAL''') + !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')) + ) + } + 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(stub(123123), test(optional(123123)))}", + "message" : "User not found by email = [${value(server(regex(email())), client('not.existing@user.com'))}]" + } + """ + ) + } + } + ] + } + @Issue('72') def "should make the execute method work"() { given: 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 053d33217b..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 { @@ -1293,6 +1294,99 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie stubMappingIsValidWireMockStub(wireMockStub) } + @Issue('42') + @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')) + ) + } + 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(stub(123123), test(optional(123123)))}", + "message" : "User not found by email = [${value(server(regex(email())), client('not.existing@user.com'))}]" + } + """ + ) + } + } + ] + } + String toJsonString(value) { new JsonBuilder(value).toPrettyString() } 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') } }