From ebf09dfda3607f8e5869eae9ec922e3c31d7bf16 Mon Sep 17 00:00:00 2001 From: Peter Frank Date: Mon, 20 Mar 2017 10:02:40 -0700 Subject: [PATCH] gh-245: Created new DslProperty creation methods (#247) Added: - iso8601WithOffset which handles iso8601 date time with timezone date validation - nonBlankString which handles string that aren't "" and also just whitespace - nonEmptyString which handles "" strings - anyOf(String...) which handles enumerated style attributes for validation fixes #245 changed the name of iso8601DatetimeWithTimezone to anyIso8601WithOffset per code review suggestions --- .../internal/PatternValueDslProperty.groovy | 17 ++++++++ .../spec/internal/RegexPatterns.groovy | 19 ++++++++ .../spec/internal/RegexPatternsSpec.groovy | 43 +++++++++++++++++++ .../JaxRsClientMethodBuilderSpec.groovy | 22 +++++++--- .../MockMvcMethodBodyBuilderSpec.groovy | 22 +++++++--- 5 files changed, 113 insertions(+), 10 deletions(-) diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.groovy index f00d27f6da..72a8e85df0 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.groovy @@ -83,6 +83,23 @@ abstract class PatternValueDslProperty { return createAndValidateProperty(RegexPatterns.ANY_TIME, "12:2$d:3$d") } + T anyIso8601WithOffset() { + int d = this.random.nextInt(8) + 1 + return createAndValidateProperty(RegexPatterns.ISO8601_WITH_OFFSET, "201$d-0$d-1${d}T12:23:34.123Z") + } + + T anyNonBlankString() { + return createAndValidateProperty(RegexPatterns.NON_BLANK, randomString(20)) + } + + T anyNonEmptyString() { + return createAndValidateProperty(RegexPatterns.NON_EMPTY, randomString(20)) + } + + T anyOf(String... values){ + return createAndValidateProperty(RegexPatterns.anyOf(values), values[0]) + } + private static String randomString(int length) { char[] characterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray() Random random = new Random() diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy index 81dce74cb3..370c5c7449 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy @@ -44,6 +44,13 @@ class RegexPatterns { protected static final Pattern ANY_DATE = Pattern.compile('(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])') protected static final Pattern ANY_DATE_TIME = Pattern.compile('([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') protected static final Pattern ANY_TIME = Pattern.compile('(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') + protected static final Pattern NON_EMPTY = Pattern.compile(/.+/) + protected static final Pattern NON_BLANK = Pattern.compile(/.*(\S+|\R).*|!^\R*$/) + protected static final Pattern ISO8601_WITH_OFFSET = Pattern.compile(/([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.\d{3})?(Z|[+-][01]\d:[0-5]\d)/) + + protected static Pattern anyOf(String... values){ + return Pattern.compile(values.collect({"^$it\$"}).join("|")) + } String onlyAlphaUnicode() { return ONLY_ALPHA_UNICODE.pattern() @@ -89,6 +96,18 @@ class RegexPatterns { return ANY_TIME.pattern() } + String iso8601WithOffset() { + return ISO8601_WITH_OFFSET.pattern() + } + + String nonEmpty() { + return NON_EMPTY.pattern() + } + + String nonBlank() { + return NON_BLANK.pattern() + } + // end::regexps[] static String multipartParam(Object name, Object value) { diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy index d313145e5b..53255eb140 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy @@ -146,4 +146,47 @@ class RegexPatternsSpec extends Specification { "23:60:45" || false "23:59:60" || false } + + def "should generate a regex with iso8601DateTimeWithTimezone [#textToMatch] in YYYY-MM-DDTHH:mm:ss.SSSZZ format that should match [#shouldMatch]"(){ + expect: + shouldMatch == Pattern.compile(regexPatterns.iso8601WithOffset()).matcher(textToMatch).matches() + where: + textToMatch || shouldMatch + '2014-03-01T12:23:45Z' || true + '2014-03-01T12:23:45+01:00' || true + '2014-03-01T12:23:45.123Z' || true + '2014-03-01T12:23:45.123+01:00' || true + '2014-03-01T12:23:45' || false + '2014-03-01T12:23:45.123' || false + } + + def "should generate a regex for a non blank string [#textToMatch] that should match [#shouldMatch]"(){ + expect: + shouldMatch == Pattern.compile(regexPatterns.nonBlank()).matcher(textToMatch).matches() + where: + textToMatch || shouldMatch + 'Not Empty' || true + '' || false + ' ' || false + } + + def "should generate a regex for a non empty string [#textToMatch] that should match [#shouldMatch]"() { + expect: + shouldMatch == Pattern.compile(regexPatterns.nonEmpty()).matcher(textToMatch).matches() + where: + textToMatch || shouldMatch + 'Not Empty' || true + '' || false + ' ' || true + } + + def "should generate a regex for an enumerated value [#textToMatch] that should match [#shouldMatch]"(){ + expect: + shouldMatch == RegexPatterns.anyOf('foo', 'bar').matcher(textToMatch).matches() + where: + textToMatch || shouldMatch + 'foo' || true + 'bar' || true + 'baz' || false + } } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy index 827e05480c..9a2fa71b7f 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy @@ -997,7 +997,11 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub uuid: $(anyUuid()), date: $(anyDate()), dateTime: $(anyDateTime()), - time: $(anyTime()) + time: $(anyTime()), + iso8601WithOffset: $(anyIso8601WithOffset()), + nonBlankString: $(anyNonBlankString()), + nonEmptyString: $(anyNonEmptyString()), + anyOf: $(anyOf('foo', 'bar')) ]) headers { contentType(applicationJson()) @@ -1016,7 +1020,11 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub uuid: $(anyUuid()), date: $(anyDate()), dateTime: $(anyDateTime()), - time: $(anyTime()) + time: $(anyTime()), + iso8601WithOffset: $(anyIso8601WithOffset()), + nonBlankString: $(anyNonBlankString()), + nonEmptyString: $(anyNonEmptyString()), + anyOf: $(anyOf('foo', 'bar')) ]) headers { contentType(applicationJson()) @@ -1040,13 +1048,17 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub test.contains('assertThatJson(parsedJson).field("date").matches("(\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])")') test.contains('assertThatJson(parsedJson).field("dateTime").matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])")') test.contains('assertThatJson(parsedJson).field("time").matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])")') + test.contains('assertThatJson(parsedJson).field("iso8601WithOffset").matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\\\.\\\\d{3})?(Z|[+-][01]\\\\d:[0-5]\\\\d)")') + test.contains('assertThatJson(parsedJson).field("nonBlankString").matches(".*(\\\\S+|\\\\R).*|!^\\\\R*' + endOfLineRegexSymbol + '")') + test.contains('assertThatJson(parsedJson).field("nonEmptyString").matches(".+")') + test.contains('assertThatJson(parsedJson).field("anyOf").matches("^foo' + endOfLineRegexSymbol + '|^bar' + endOfLineRegexSymbol + '")') !test.contains('cursor') and: SyntaxChecker.tryToCompile(methodBuilderName, blockBuilder.toString()) where: - methodBuilderName | methodBuilder - "JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) } - "JaxRsClientJUnitMethodBodyBuilder" | { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } + methodBuilderName | methodBuilder | endOfLineRegexSymbol + "JaxRsClientSpockMethodRequestProcessingBodyBuilder"| { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$' + "JaxRsClientJUnitMethodBodyBuilder" | { org.springframework.cloud.contract.spec.Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | '$' } private String stripped(String string) { diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy index 6b7503e697..f972024375 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy @@ -1898,7 +1898,11 @@ World.'''""" uuid: $(anyUuid()), date: $(anyDate()), dateTime: $(anyDateTime()), - time: $(anyTime()) + time: $(anyTime()), + iso8601WithOffset: $(anyIso8601WithOffset()), + nonBlankString: $(anyNonBlankString()), + nonEmptyString: $(anyNonEmptyString()), + anyOf: $(anyOf('foo', 'bar')) ]) headers { contentType(applicationJson()) @@ -1917,7 +1921,11 @@ World.'''""" uuid: $(anyUuid()), date: $(anyDate()), dateTime: $(anyDateTime()), - time: $(anyTime()) + time: $(anyTime()), + iso8601WithOffset: $(anyIso8601WithOffset()), + nonBlankString: $(anyNonBlankString()), + nonEmptyString: $(anyNonEmptyString()), + anyOf: $(anyOf('foo', 'bar')) ]) headers { contentType(applicationJson()) @@ -1941,13 +1949,17 @@ World.'''""" test.contains('assertThatJson(parsedJson).field("date").matches("(\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])') test.contains('assertThatJson(parsedJson).field("dateTime").matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') test.contains('assertThatJson(parsedJson).field("time").matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])")') + test.contains('assertThatJson(parsedJson).field("iso8601WithOffset").matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\\\.\\\\d{3})?(Z|[+-][01]\\\\d:[0-5]\\\\d)")') + test.contains('assertThatJson(parsedJson).field("nonBlankString").matches(".*(\\\\S+|\\\\R).*|!^\\\\R*' + endOfLineRegExSymbol + '")') + test.contains('assertThatJson(parsedJson).field("nonEmptyString").matches(".+")') + test.contains('assertThatJson(parsedJson).field("anyOf").matches("^foo' + endOfLineRegExSymbol + '|^bar' + endOfLineRegExSymbol + '")') !test.contains('cursor') and: SyntaxChecker.tryToCompile(methodBuilderName, blockBuilder.toString()) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } - "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) } + methodBuilderName | methodBuilder | endOfLineRegExSymbol + "MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$' + "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) } | '$' }