diff --git a/consumer/README.md b/consumer/README.md index b2ad057342..f1db40d93e 100644 --- a/consumer/README.md +++ b/consumer/README.md @@ -354,13 +354,6 @@ DslPart body = new PactDslJsonBody() For an example, have a look at [WildcardKeysTest](https://github.com/DiUS/pact-jvm/blob/master/consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/WildcardKeysTest.java). -**NOTE:** The `eachKeyLike` method adds a `*` to the matching path, so the matching definition will be applied to all keys - of the map if there is not a more specific matcher defined for a particular key. Having more than one `eachKeyLike` condition - applied to a map will result in only one being applied when the pact is verified (probably the last). - -**Further Note: From version 3.5.22 onwards pacts with wildcards applied to map keys will require the Java system property -"pact.matching.wildcard" set to value "true" when the pact file is verified.** - ### Matching on paths You can use regular expressions to match incoming requests. The DSL has a `matchPath` method for this. You can provide diff --git a/consumer/groovy/README.md b/consumer/groovy/README.md index 39f06c1fa2..172ad25663 100644 --- a/consumer/groovy/README.md +++ b/consumer/groovy/README.md @@ -520,13 +520,6 @@ withBody { For an example, have a look at [WildcardPactSpec](https://github.com/DiUS/pact-jvm/blob/master/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/WildcardPactSpec.groovy). -**NOTE:** The `keyLike` method adds a `*` to the matching path, so the matching definition will be applied to all keys - of the map if there is not a more specific matcher defined for a particular key. Having more than one `keyLike` condition - applied to a map will result in only one being applied when the pact is verified (probably the last). - -**Further Note: From version 3.5.22 onwards pacts with wildcards applied to map keys will require the Java system property -"pact.matching.wildcard" set to value "true" when the pact file is verified.** - ### Matching with an OR The V3 spec allows multiple matchers to be combined using either AND or OR for a value. The main use of this would be to diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/PactBodyBuilder.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/PactBodyBuilder.groovy index ebdccae83f..8ad2883fba 100755 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/PactBodyBuilder.groovy +++ b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/PactBodyBuilder.groovy @@ -1,7 +1,5 @@ package au.com.dius.pact.consumer.groovy -import au.com.dius.pact.core.model.Feature -import au.com.dius.pact.core.model.FeatureToggles import au.com.dius.pact.core.model.generators.Category import au.com.dius.pact.core.model.generators.Generators import au.com.dius.pact.core.model.generators.ProviderStateGenerator @@ -289,17 +287,12 @@ class PactBodyBuilder extends GroovyBuilder { } /** - * Matches the values of the map ignoring the keys. Note: this needs the Java system property - * "pact.matching.wildcard" set to value "true" when the pact file is verified. + * Matches the values of the map ignoring the keys. */ def keyLike(String key, def value) { - if (FeatureToggles.isFeatureSet(Feature.UseMatchValuesMatcher)) { - setMatcherAttribute(new ValuesMatcher(), path) - if (value instanceof Closure) { - bodyRepresentation[key] = invokeClosure(value, buildPath(STAR)) - } else { - addAttribute(key, STAR, value) - } + setMatcherAttribute(new ValuesMatcher(), path) + if (value instanceof Closure) { + bodyRepresentation[key] = invokeClosure(value, buildPath(STAR)) } else { addAttribute(key, STAR, value) } diff --git a/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/WildcardPactSpec.groovy b/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/ValuesMatcherPactSpec.groovy similarity index 92% rename from consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/WildcardPactSpec.groovy rename to consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/ValuesMatcherPactSpec.groovy index dde0a129b7..5ef72a9d0b 100644 --- a/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/WildcardPactSpec.groovy +++ b/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/ValuesMatcherPactSpec.groovy @@ -1,7 +1,6 @@ package au.com.dius.pact.consumer.groovy import au.com.dius.pact.consumer.PactVerificationResult -import au.com.dius.pact.core.model.FeatureToggles import groovy.json.JsonSlurper import groovyx.net.http.ContentTypes import groovyx.net.http.FromServer @@ -11,10 +10,10 @@ import spock.lang.Specification import static groovyx.net.http.ContentTypes.JSON @SuppressWarnings(['AbcMetric']) -class WildcardPactSpec extends Specification { +class ValuesMatcherPactSpec extends Specification { @SuppressWarnings(['NestedBlockDepth']) - def 'pact test requiring wildcards'() { + def 'pact test using values matcher'() { given: def articleService = new PactBuilder() articleService { @@ -74,21 +73,22 @@ class WildcardPactSpec extends Specification { articleService.interactions[0].response.matchingRules.rulesForCategory('body').matchingRules.keySet() == [ '$.articles', '$.articles[*].variants', + '$.articles[*].variants[*]', '$.articles[*].variants[*].*', '$.articles[*].variants[*].*[*].bundles', - '$.articles[*].variants[*].*[*].bundles[*].*', + '$.articles[*].variants[*].*[*].bundles[*]', '$.articles[*].variants[*].*[*].bundles[*].*.description', '$.articles[*].variants[*].*[*].bundles[*].*.referencedArticles', '$.articles[*].variants[*].*[*].bundles[*].*.referencedArticles[*].bundleId', + '$.articles[*].variants[*].*[*].bundles[*].*.referencedArticles[*]', '$.articles[*].variants[*].*[*].bundles[*].*.referencedArticles[*].*' ] as Set } @SuppressWarnings(['NestedBlockDepth']) - def 'key like test with useMatchValuesMatcher turned on'() { + def 'key like test'() { given: - FeatureToggles.toggleFeature('pact.feature.matchers.useMatchValuesMatcher', true) def articleService = new PactBuilder() articleService { serviceConsumer 'ArticleConsumer' @@ -144,9 +144,5 @@ class WildcardPactSpec extends Specification { '$.events.*.references.*', '$.events.*.references.*[*].eventId' ] as Set - - cleanup: - FeatureToggles.reset() - } } diff --git a/consumer/junit/README.md b/consumer/junit/README.md index 195e6eaa28..0d980e242a 100644 --- a/consumer/junit/README.md +++ b/consumer/junit/README.md @@ -561,13 +561,6 @@ DslPart body = new PactDslJsonBody() For an example, have a look at [WildcardKeysTest](https://github.com/DiUS/pact-jvm/blob/master/consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/WildcardKeysTest.java). -**NOTE:** The `eachKeyLike` method adds a `*` to the matching path, so the matching definition will be applied to all keys - of the map if there is not a more specific matcher defined for a particular key. Having more than one `eachKeyLike` condition - applied to a map will result in only one being applied when the pact is verified (probably the last). - -**Further Note: From version 3.5.22 onwards pacts with wildcards applied to map keys will require the Java system property -"pact.matching.wildcard" set to value "true" when the pact file is verified.** - #### Combining matching rules with AND/OR Matching rules can be combined with AND/OR. There are two methods available on the DSL for this. For example: diff --git a/consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/WildcardKeysTest.java b/consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/ValueMatcherTest.java similarity index 90% rename from consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/WildcardKeysTest.java rename to consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/ValueMatcherTest.java index 5844fbaabd..a588d99f4a 100644 --- a/consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/WildcardKeysTest.java +++ b/consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/ValueMatcherTest.java @@ -21,14 +21,14 @@ import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.is; -public class WildcardKeysTest { +public class ValueMatcherTest { private static final String APPLICATION_JSON = "application/json"; @Rule - public PactProviderRule provider = new PactProviderRule("WildcardKeysProvider", "localhost", 8111, this); + public PactProviderRule provider = new PactProviderRule("ValueMatcherProvider", this); - @Pact(provider="WildcardKeysProvider", consumer="WildcardKeysConsumer") + @Pact(provider="ValueMatcherProvider", consumer="ValueMatcherConsumer") public RequestResponsePact createFragment(PactDslWithProvider builder) { DslPart body = new PactDslJsonBody() .eachLike("articles") @@ -67,13 +67,15 @@ public RequestResponsePact createFragment(PactDslWithProvider builder) { MatcherTestUtils.assertResponseMatcherKeysEqualTo(pact, "body", "$.articles", "$.articles[*].variants", - "$.articles[*].variants[*].*", + "$.articles[*].variants[*]", "$.articles[*].variants[*].*[*].bundles", - "$.articles[*].variants[*].*[*].bundles[*].*", + "$.articles[*].variants[*].*[*].bundles[*]", "$.articles[*].variants[*].*[*].bundles[*].*.description", "$.articles[*].variants[*].*[*].bundles[*].*.referencedArticles", + "$.articles[*].variants[*].*[*].bundles[*].*.referencedArticles[*]", "$.articles[*].variants[*].*[*].bundles[*].*.referencedArticles[*].*", "$.articles[*].variants[*].*[*].bundles[*].*.referencedArticles[*].bundleId", + "$.foo", "$.foo.*" ); @@ -81,9 +83,9 @@ public RequestResponsePact createFragment(PactDslWithProvider builder) { } @Test - @PactVerification("WildcardKeysProvider") + @PactVerification("ValueMatcherProvider") public void runTest() throws IOException { - String result = Request.Get("http://localhost:8111/") + String result = Request.Get(provider.getUrl()) .addHeader("Accept", APPLICATION_JSON) .execute().returnContent().asString(); Map body = (Map) new JsonSlurper().parseText(result); diff --git a/consumer/src/main/java/au/com/dius/pact/consumer/dsl/LambdaDslObject.java b/consumer/src/main/java/au/com/dius/pact/consumer/dsl/LambdaDslObject.java index c6a331f041..648497f63d 100644 --- a/consumer/src/main/java/au/com/dius/pact/consumer/dsl/LambdaDslObject.java +++ b/consumer/src/main/java/au/com/dius/pact/consumer/dsl/LambdaDslObject.java @@ -943,7 +943,6 @@ public LambdaDslObject eachArrayWithMinMaxLike(String name, Integer minSize, Int /** * Accepts any key, and each key is mapped to a list of items that must match the following object definition. - * Note: this needs the Java system property "pact.matching.wildcard" set to value "true" when the pact file is verified. * * @param exampleKey Example key to use for generating bodies */ @@ -957,7 +956,6 @@ public LambdaDslObject eachKeyMappedToAnArrayLike(String exampleKey, Consumer /** * Accepts any key, and each key is mapped to a map that must match the provided object definition - * Note: this needs the Java system property "pact.matching.wildcard" set to value "true" when the pact file is verified. * * @param exampleKey Example key to use for generating bodies * @param value Value to use for matching and generated bodies diff --git a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslJsonBody.kt b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslJsonBody.kt index b04c53873a..371e3d27ef 100755 --- a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslJsonBody.kt +++ b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslJsonBody.kt @@ -1109,54 +1109,38 @@ open class PactDslJsonBody : DslPart { /** * Accepts any key, and each key is mapped to a list of items that must match the following object definition - * Note: this needs the Java system property "pact.matching.wildcard" set to value "true" when the pact file - * is verified. * @param exampleKey Example key to use for generating bodies */ fun eachKeyMappedToAnArrayLike(exampleKey: String): PactDslJsonBody { - if (isFeatureSet(Feature.UseMatchValuesMatcher)) { - matchers.addRule( - if (rootPath.endsWith(".")) rootPath.substring(0, rootPath.length - 1) else rootPath, ValuesMatcher - ) - } else { - matchers.addRule("$rootPath*", TypeMatcher) - } + matchers.addRule( + if (rootPath.endsWith(".")) rootPath.substring(0, rootPath.length - 1) else rootPath, ValuesMatcher + ) val parent = PactDslJsonArray("$rootPath*", exampleKey, this, true) return PactDslJsonBody(".", "", parent) } /** * Accepts any key, and each key is mapped to a map that must match the following object definition - * Note: this needs the Java system property "pact.matching.wildcard" set to value "true" when the pact file - * is verified. * @param exampleKey Example key to use for generating bodies */ fun eachKeyLike(exampleKey: String): PactDslJsonBody { - if (isFeatureSet(Feature.UseMatchValuesMatcher)) { - matchers.addRule( - if (rootPath.endsWith(".")) rootPath.substring(0, rootPath.length - 1) else rootPath, ValuesMatcher - ) - } else { - matchers.addRule("$rootPath*", TypeMatcher) - } + matchers.addRule( + if (rootPath.endsWith(".")) rootPath.substring(0, rootPath.length - 1) else rootPath, ValuesMatcher + ) return PactDslJsonBody("$rootPath*.", exampleKey, this) } /** * Accepts any key, and each key is mapped to a map that must match the provided object definition - * Note: this needs the Java system property "pact.matching.wildcard" set to value "true" when the pact file - * is verified. * @param exampleKey Example key to use for generating bodies * @param value Value to use for matching and generated bodies */ fun eachKeyLike(exampleKey: String, value: PactDslJsonRootValue): PactDslJsonBody { val body = body as JsonValue.Object body.add(exampleKey, value.body) - if (isFeatureSet(Feature.UseMatchValuesMatcher)) { - matchers.addRule( - if (rootPath.endsWith(".")) rootPath.substring(0, rootPath.length - 1) else rootPath, ValuesMatcher - ) - } + matchers.addRule( + if (rootPath.endsWith(".")) rootPath.substring(0, rootPath.length - 1) else rootPath, ValuesMatcher + ) for (matcherName in value.matchers.matchingRules.keys) { matchers.addRules("$rootPath*$matcherName", value.matchers.matchingRules[matcherName]!!.rules) } diff --git a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/LambdaDslSpec.groovy b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/LambdaDslSpec.groovy index fa9acf8e68..6a0294cecf 100644 --- a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/LambdaDslSpec.groovy +++ b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/LambdaDslSpec.groovy @@ -107,7 +107,7 @@ class LambdaDslSpec extends Specification { def result = LambdaDsl.newJsonBody(jsonObject).build() then: - result.matchers.matchingRules.keySet() == ['.offer.prices.*', '.offer.shippingCosts.*'] as Set + result.matchers.matchingRules.keySet() == ['.offer.prices', '.offer.prices.*', '.offer.shippingCosts'] as Set result.toString() == '{"offer":{"prices":{"DE":1620},"shippingCosts":{"DE":{"cia":300}}}}' } diff --git a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslJsonBodySpec.groovy b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslJsonBodySpec.groovy index ebe4ca89ba..e1f2a51270 100644 --- a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslJsonBodySpec.groovy +++ b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactDslJsonBodySpec.groovy @@ -1,7 +1,5 @@ package au.com.dius.pact.consumer.dsl -import au.com.dius.pact.core.model.Feature -import au.com.dius.pact.core.model.FeatureToggles import au.com.dius.pact.core.model.PactSpecVersion import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup import au.com.dius.pact.core.model.matchingrules.RegexMatcher @@ -214,46 +212,8 @@ class PactDslJsonBodySpec extends Specification { ] } - def 'eachKey - generate a wildcard matcher pattern if useMatchValuesMatcher is not set'() { + def 'eachKey - generate a match values matcher'() { given: - FeatureToggles.toggleFeature(Feature.UseMatchValuesMatcher, false) - - def pactDslJsonBody = new PactDslJsonBody() - .object('one') - .eachKeyLike('key1') - .id() - .closeObject() - .closeObject() - .object('two') - .eachKeyLike('key2', PactDslJsonRootValue.stringMatcher('\\w+', 'test')) - .closeObject() - .object('three') - .eachKeyMappedToAnArrayLike('key3') - .id('key3-id') - .closeObject() - .closeArray() - .closeObject() - - when: - pactDslJsonBody.close() - - then: - pactDslJsonBody.matchers.matchingRules == [ - '$.one.*': new MatchingRuleGroup([TypeMatcher.INSTANCE]), - '$.one.*.id': new MatchingRuleGroup([TypeMatcher.INSTANCE]), - '$.two.*': new MatchingRuleGroup([new RegexMatcher('\\w+', 'test')]), - '$.three.*': new MatchingRuleGroup([TypeMatcher.INSTANCE]), - '$.three.*[*].key3-id': new MatchingRuleGroup([TypeMatcher.INSTANCE]) - ] - - cleanup: - FeatureToggles.reset() - } - - def 'eachKey - generate a match values matcher if useMatchValuesMatcher is set'() { - given: - FeatureToggles.toggleFeature(Feature.UseMatchValuesMatcher, true) - def pactDslJsonBody = new PactDslJsonBody() .object('one') .eachKeyLike('key1') @@ -282,9 +242,6 @@ class PactDslJsonBodySpec extends Specification { '$.three': new MatchingRuleGroup([ValuesMatcher.INSTANCE]), '$.three.*[*].key3-id': new MatchingRuleGroup([TypeMatcher.INSTANCE]) ] - - cleanup: - FeatureToggles.reset() } def 'Allow an attribute to be defined from a DSL part'() { diff --git a/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/Matchers.kt b/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/Matchers.kt index 9326e2cad5..ac3831cb8d 100755 --- a/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/Matchers.kt +++ b/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/Matchers.kt @@ -18,6 +18,7 @@ import au.com.dius.pact.core.model.matchingrules.MaxEqualsIgnoreOrderMatcher import au.com.dius.pact.core.model.matchingrules.MinEqualsIgnoreOrderMatcher import au.com.dius.pact.core.model.matchingrules.MinMaxEqualsIgnoreOrderMatcher import au.com.dius.pact.core.model.matchingrules.TypeMatcher +import au.com.dius.pact.core.model.matchingrules.ValuesMatcher import au.com.dius.pact.core.model.parsePath import mu.KLogging import java.math.BigInteger @@ -26,7 +27,6 @@ import java.util.function.Predicate object Matchers : KLogging() { - const val PACT_MATCHING_WILDCARD = "pact.matching.wildcard" private val intRegex = Regex("\\d+") private fun matchesToken(pathElement: String, token: PathToken): Int { @@ -132,12 +132,6 @@ object Matchers : KLogging() { resolvedMatchers.matchingRules.keys.any { entry -> entry.endsWith(".*") } } else false - /** - * If wildcard matching logic is enabled (where keys are ignored and only values are compared) - */ - @JvmStatic - fun wildcardMatchingEnabled() = System.getProperty(PACT_MATCHING_WILDCARD)?.trim() == "true" - @JvmStatic @JvmOverloads fun domatch( @@ -200,7 +194,7 @@ object Matchers : KLogging() { callback: (List, T?, T?) -> List ): List { val result = mutableListOf() - if (wildcardMatchingEnabled() && context.wildcardMatcherDefined(path + "any")) { + if (matcher is ValuesMatcher) { actualEntries.entries.forEach { (key, value) -> if (expectedEntries.containsKey(key)) { result.addAll(callback(path + key, expectedEntries[key]!!, value)) @@ -297,37 +291,26 @@ object Matchers : KLogging() { } /** - * Compares any "extra" actual elements to expected using wildcard - * matching. + * Compares any "extra" actual elements to expected */ - private fun wildcardCompare( - basePath: List, + private fun compareActualElements( + path: List, + actualIndex: Int, expectedValues: List, actual: T, context: MatchingContext, callback: (List, T, T, MatchingContext) -> List ): List { - val path = basePath + expectedValues.size.toString() - val pathStr = path.joinToString(".") - val starPathStr = (basePath + "*").joinToString(".") - - return callback(path, expectedValues[0], actual, context) - .map { matchResult -> - // replaces index with '*' for clearer errors - matchResult.copy( - key = matchResult.key.replaceFirst(pathStr, starPathStr), - result = matchResult.result.map { mismatch -> - mismatch.copy(path = mismatch.path.replaceFirst(pathStr, starPathStr)) - }) - } + val indexPath = path + actualIndex.toString() + return if (context.directMatcherDefined(indexPath)) { + callback(indexPath, expectedValues[0], actual, context) + } else { + emptyList() + } } /** * Compares every permutation of actual against expected. - * - * If actual has more elements than expected, and there is a wildcard matcher, - * then each extra actual element is compared to the first expected element - * using the wildcard matcher. */ fun compareListContentUnordered( expectedList: List, @@ -337,11 +320,8 @@ object Matchers : KLogging() { generateDiff: () -> String, callback: (List, T, T, MatchingContext) -> List ): List { - val doWildCardMatching = actualList.size > expectedList.size && - context.wildcardIndexMatcherDefined(path + expectedList.size.toString()) - - val memoizedWildcardCompare = { actualIndex: Int -> - wildcardCompare(path, expectedList, actualList[actualIndex], context, callback) + val memoizedActualCompare = { actualIndex: Int -> + compareActualElements(path, actualIndex, expectedList, actualList[actualIndex], context, callback) }.memoizeFixed(actualList.size) val memoizedCompare = { expectedIndex: Int, actualIndex: Int -> @@ -375,19 +355,23 @@ object Matchers : KLogging() { } else { examinedActualIndicesCombos.add(actualIndices.comboId) longestMatch.useIfLarger(expectedIndex, actualIndices) - if (expectedIndex < expectedList.size) { - actualIndices.indices().any { actualIndex -> - memoizedCompare(expectedIndex, actualIndex).all { - it.result.isEmpty() - } && hasMatchingPermutation(expectedIndex + 1, actualIndices - actualIndex) + when { + expectedIndex < expectedList.size -> { + actualIndices.indices().any { actualIndex -> + memoizedCompare(expectedIndex, actualIndex).all { + it.result.isEmpty() + } && hasMatchingPermutation(expectedIndex + 1, actualIndices - actualIndex) + } } - } else if (doWildCardMatching) { - actualIndices.indices().all { actualIndex -> - memoizedWildcardCompare(actualIndex).all { - it.result.isEmpty() + actualList.size > expectedList.size -> { + actualIndices.indices().all { actualIndex -> + memoizedActualCompare(actualIndex).all { + it.result.isEmpty() + } } } - } else true + else -> true + } } } @@ -400,8 +384,8 @@ object Matchers : KLogging() { val remainingErrors = smallestCombo.indices().map { actualIndex -> (longestMatch until expectedList.size).map { expectedIndex -> memoizedCompare(expectedIndex, actualIndex).flatMap { it.result } - }.flatten() + if (doWildCardMatching) { - memoizedWildcardCompare(actualIndex).flatMap { it.result } + }.flatten() + if (actualList.size > expectedList.size) { + memoizedActualCompare(actualIndex).flatMap { it.result } } else emptyList() }.toList().flatten() .groupBy { it.path } diff --git a/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/Matching.kt b/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/Matching.kt index e68988e487..db1f652b30 100644 --- a/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/Matching.kt +++ b/core/matchers/src/main/kotlin/au/com/dius/pact/core/matchers/Matching.kt @@ -5,6 +5,7 @@ import au.com.dius.pact.core.model.Request import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup import au.com.dius.pact.core.model.matchingrules.TypeMatcher +import au.com.dius.pact.core.model.parsePath import mu.KLogging data class MatchingContext(val matchers: MatchingRuleCategory, val allowUnexpectedKeys: Boolean) { @@ -45,20 +46,6 @@ data class MatchingContext(val matchers: MatchingRuleCategory, val allowUnexpect } } - fun wildcardMatcherDefined(path: List): Boolean { - val resolvedMatchers = matchers.filter { - Matchers.matchesPath(it, path) == path.size - } - return resolvedMatchers.matchingRules.keys.any { entry -> entry.endsWith(".*") } - } - - fun wildcardIndexMatcherDefined(path: List): Boolean { - val resolvedMatchers = matchers.filter { - Matchers.matchesPath(it, path) == path.size - } - return resolvedMatchers.matchingRules.keys.any { entry -> entry.endsWith("[*]") } - } - fun typeMatcherDefined(path: List): Boolean { val resolvedMatchers = resolveMatchers(path, Comparator.naturalOrder()) return resolvedMatchers.allMatchingRules().any { it is TypeMatcher } @@ -70,28 +57,33 @@ data class MatchingContext(val matchers: MatchingRuleCategory, val allowUnexpect actualEntries: Map, generateDiff: () -> String ): List { - val p = path + "any" - return if (!Matchers.wildcardMatchingEnabled() || !wildcardMatcherDefined(p)) { - val expectedKeys = expectedEntries.keys.sorted() - val actualKeys = actualEntries.keys - val actualKeysSorted = actualKeys.sorted() - val missingKeys = expectedKeys.filter { key -> !actualKeys.contains(key) } - if (allowUnexpectedKeys && missingKeys.isNotEmpty()) { - listOf(BodyItemMatchResult(path.joinToString("."), listOf(BodyMismatch(expectedEntries, actualEntries, - "Actual map is missing the following keys: ${missingKeys.joinToString(", ")}", - path.joinToString("."), generateDiff())))) - } else if (!allowUnexpectedKeys && expectedKeys != actualKeysSorted) { - listOf(BodyItemMatchResult(path.joinToString("."), listOf(BodyMismatch(expectedEntries, actualEntries, - "Expected a Map with keys $expectedKeys " + - "but received one with keys $actualKeysSorted", - path.joinToString("."), generateDiff())))) - } else { - emptyList() - } + val expectedKeys = expectedEntries.keys.sorted() + val actualKeys = actualEntries.keys + val actualKeysSorted = actualKeys.sorted() + val missingKeys = expectedKeys.filter { key -> !actualKeys.contains(key) } + return if (allowUnexpectedKeys && missingKeys.isNotEmpty()) { + listOf(BodyItemMatchResult(path.joinToString("."), listOf(BodyMismatch(expectedEntries, actualEntries, + "Actual map is missing the following keys: ${missingKeys.joinToString(", ")}", + path.joinToString("."), generateDiff())))) + } else if (!allowUnexpectedKeys && expectedKeys != actualKeysSorted) { + listOf(BodyItemMatchResult(path.joinToString("."), listOf(BodyMismatch(expectedEntries, actualEntries, + "Expected a Map with keys $expectedKeys " + + "but received one with keys $actualKeysSorted", + path.joinToString("."), generateDiff())))) } else { emptyList() } } + + /** + * Matcher defined at that path (ignoring parents) + */ + fun directMatcherDefined(path: List, pathComparator: Comparator = Comparator.naturalOrder()): Boolean { + val resolveMatchers = resolveMatchers(path, pathComparator).filter { + parsePath(it).size == path.size + } + return resolveMatchers.isNotEmpty() + } } object Matching : KLogging() { diff --git a/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/JsonBodyMatcherSpec.groovy b/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/JsonBodyMatcherSpec.groovy index b57bd86e8a..e489850021 100644 --- a/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/JsonBodyMatcherSpec.groovy +++ b/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/JsonBodyMatcherSpec.groovy @@ -9,6 +9,7 @@ import au.com.dius.pact.core.model.matchingrules.MinEqualsIgnoreOrderMatcher import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher import au.com.dius.pact.core.model.matchingrules.RegexMatcher import au.com.dius.pact.core.model.matchingrules.TypeMatcher +import au.com.dius.pact.core.model.matchingrules.ValuesMatcher import spock.lang.Ignore import spock.lang.Issue import spock.lang.Specification @@ -255,11 +256,10 @@ class JsonBodyMatcherSpec extends Specification { expectedBody = OptionalBody.body('{"something": 101}'.bytes) } - @RestoreSystemProperties - def 'matching json bodies - with a matcher defined - and when the actual body is missing a key, not be a mismatch'() { + @SuppressWarnings('LineLength') + def 'matching json bodies - with a Values matcher defined - and when the actual body is missing a key, not be a mismatch'() { given: - context.matchers.addRule('$.*', TypeMatcher.INSTANCE) - System.setProperty(Matchers.PACT_MATCHING_WILDCARD, 'true') + context.matchers.addRule('$', ValuesMatcher.INSTANCE) expect: matcher.matchBody(expectedBody, actualBody, context).mismatches.empty @@ -276,7 +276,6 @@ class JsonBodyMatcherSpec extends Specification { given: context.matchers.addRule('$', new MinTypeMatcher(1)) context.matchers.addRule('$[*].*', TypeMatcher.INSTANCE) - System.setProperty(Matchers.PACT_MATCHING_WILDCARD, 'false') when: def result = matcher.matchBody(expectedBody, actualBody, context) @@ -314,12 +313,10 @@ class JsonBodyMatcherSpec extends Specification { }]'''.bytes) } - @RestoreSystemProperties def 'returns a mismatch - when comparing maps with different keys and wildcard matching is disabled'() { given: context = new MatchingContext(new MatchingRuleCategory('body'), false) context.matchers.addRule('$.*', new MinTypeMatcher(0)) - System.setProperty(Matchers.PACT_MATCHING_WILDCARD, 'false') expect: matcher.matchBody(expectedBody, actualBody, context).mismatches.find { @@ -334,10 +331,9 @@ class JsonBodyMatcherSpec extends Specification { } @RestoreSystemProperties - def 'returns no mismatch - when comparing maps with different keys and wildcard matching is enabled'() { + def 'returns no mismatch - when comparing maps with different keys and Value matcher is enabled'() { given: - context.matchers.addRule('$.*', new MinTypeMatcher(0)) - System.setProperty(Matchers.PACT_MATCHING_WILDCARD, 'true') + context.matchers.addRule('$', ValuesMatcher.INSTANCE) expect: matcher.matchBody(expectedBody, actualBody, context).mismatches.empty @@ -424,8 +420,7 @@ class JsonBodyMatcherSpec extends Specification { def maxSize = 3 def actualBody = OptionalBody.body(actual.bytes) def expectedBody = OptionalBody.body(expected.bytes) - context.matchers - .addRule('$', new MaxEqualsIgnoreOrderMatcher(maxSize)) + context.matchers.addRule('$', new MaxEqualsIgnoreOrderMatcher(maxSize)) when: def mismatches = matcher.matchBody(expectedBody, actualBody, context) @@ -496,7 +491,7 @@ class JsonBodyMatcherSpec extends Specification { then: mismatches.size() == 2 mismatches*.mismatch[0].matches(/Expected \[(.*)\] to match \[(.*)\] ignoring order of elements/) - mismatches*.path == ['$', '$.*'] + mismatches*.path == ['$', '$.2'] where: diff --git a/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/MatchersSpec.groovy b/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/MatchersSpec.groovy index 30980a53f1..c44306fa99 100644 --- a/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/MatchersSpec.groovy +++ b/core/matchers/src/test/groovy/au/com/dius/pact/core/matchers/MatchersSpec.groovy @@ -2,10 +2,10 @@ package au.com.dius.pact.core.matchers import au.com.dius.pact.core.model.InvalidPathExpression import au.com.dius.pact.core.model.OptionalBody -import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory import au.com.dius.pact.core.model.matchingrules.EqualsIgnoreOrderMatcher import au.com.dius.pact.core.model.matchingrules.EqualsMatcher import au.com.dius.pact.core.model.matchingrules.IncludeMatcher +import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup import au.com.dius.pact.core.model.matchingrules.MatchingRulesImpl import au.com.dius.pact.core.model.matchingrules.MinMaxEqualsIgnoreOrderMatcher @@ -15,7 +15,6 @@ import au.com.dius.pact.core.model.matchingrules.RegexMatcher import au.com.dius.pact.core.model.matchingrules.TypeMatcher import spock.lang.Specification import spock.lang.Unroll -import spock.util.environment.RestoreSystemProperties @SuppressWarnings('ClosureAsLastMethodParameter') class MatchersSpec extends Specification { @@ -217,36 +216,6 @@ class MatchersSpec extends Specification { } } - def 'wildcardMatchingEnabled - disabled by default'() { - expect: - !Matchers.wildcardMatchingEnabled() - } - - @RestoreSystemProperties - @Unroll - def 'wildcardMatchingEnabled - #enabledOrDisabled when pact.matching.wildcard = "#value"'() { - given: - def testInvocation = { String v -> - System.setProperty('pact.matching.wildcard', v) - Matchers.wildcardMatchingEnabled() - } - - expect: - testInvocation(value) == enabled - - where: - - value | enabledOrDisabled | enabled - '' | 'disabled' | false - ' ' | 'disabled' | false - 'somevalue' | 'disabled' | false - 'false' | 'disabled' | false - ' false ' | 'disabled' | false - 'true' | 'enabled' | true - ' true ' | 'enabled' | true - - } - def 'should default to a matching defined at a parent level'() { given: def matchingRules = new MatchingRulesImpl() diff --git a/provider/gradle/README.md b/provider/gradle/README.md index 77e20b8d63..63a4539c3f 100644 --- a/provider/gradle/README.md +++ b/provider/gradle/README.md @@ -95,7 +95,6 @@ The following project properties can be specified with `-Pproperty=value` on the |`pact.filter.providerState`|Only verify interactions whose provider state match the provided regular expression. An empty string matches interactions that have no state| |`pact.filter.pacturl`|This filter allows just the just the changed pact specified in a webhook to be run. It should be used in conjunction with `pact.filter.consumers` | |`pact.verifier.publishResults`|Publishing of verification results will be skipped unless this property is set to 'true'| -|`pact.matching.wildcard`|Enables matching of map values ignoring the keys when this property is set to 'true'| |`pact.verifier.disableUrlPathDecoding`|Disables decoding of request paths| |`pact.pactbroker.httpclient.usePreemptiveAuthentication`|Enables preemptive authentication with the pact broker when set to `true`| |`pact.provider.tag`|Sets the provider tag to push before publishing verification results (can use a comma separated list)| diff --git a/provider/junit/src/test/java/au/com/dius/pact/provider/junit/ArticlesContractTest.java b/provider/junit/src/test/java/au/com/dius/pact/provider/junit/ArticlesContractTest.java index 9ecd1fa51b..92210b84cb 100644 --- a/provider/junit/src/test/java/au/com/dius/pact/provider/junit/ArticlesContractTest.java +++ b/provider/junit/src/test/java/au/com/dius/pact/provider/junit/ArticlesContractTest.java @@ -1,6 +1,5 @@ package au.com.dius.pact.provider.junit; -import au.com.dius.pact.core.matchers.Matchers; import au.com.dius.pact.provider.junit.target.HttpTarget; import au.com.dius.pact.provider.junitsupport.Provider; import au.com.dius.pact.provider.junitsupport.State; @@ -11,7 +10,6 @@ import au.com.dius.pact.provider.junitsupport.target.TestTarget; import com.github.restdriver.clientdriver.ClientDriverRule; import org.apache.commons.io.IOUtils; -import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.runner.RunWith; @@ -26,7 +24,7 @@ @RunWith(PactRunner.class) @Provider("ArticlesProvider") -@PactFolder("src/test/resources/wildcards") +@PactFolder("src/test/resources/match-values") @VerificationReports({"console", "markdown"}) public class ArticlesContractTest { @@ -40,18 +38,12 @@ public class ArticlesContractTest { @Before public void before() throws IOException { - System.setProperty(Matchers.PACT_MATCHING_WILDCARD, "true"); String json = IOUtils.toString(getClass().getResourceAsStream("/articles.json"), Charset.defaultCharset()); embeddedService.addExpectation( onRequestTo("/articles.json"), giveResponse(json, "application/json") ); } - @After - public void after() { - System.clearProperty(Matchers.PACT_MATCHING_WILDCARD); - } - @State("Pact for Issue 313") public void stateChange() { LOGGER.debug("stateChange - Pact for Issue 313 - Before"); diff --git a/provider/junit/src/test/resources/wildcards/ArticlesConsumer-ArticlesProvider.json b/provider/junit/src/test/resources/match-values/ArticlesConsumer-ArticlesProvider.json similarity index 95% rename from provider/junit/src/test/resources/wildcards/ArticlesConsumer-ArticlesProvider.json rename to provider/junit/src/test/resources/match-values/ArticlesConsumer-ArticlesProvider.json index 251c84e809..1ae75d3da0 100644 --- a/provider/junit/src/test/resources/wildcards/ArticlesConsumer-ArticlesProvider.json +++ b/provider/junit/src/test/resources/match-values/ArticlesConsumer-ArticlesProvider.json @@ -39,10 +39,10 @@ ], "combine": "AND" }, - "$.articles[*].variants.*": { + "$.articles[*].variants": { "matchers": [ { - "match": "type" + "match": "values" } ], "combine": "AND" diff --git a/provider/lein/README.md b/provider/lein/README.md index 4bfb8e5575..1addb2301e 100644 --- a/provider/lein/README.md +++ b/provider/lein/README.md @@ -153,7 +153,6 @@ The following plugin options can be specified on the command line: |:pact.filter.description|Only verify interactions whose description match the provided regular expression| |:pact.filter.providerState|Only verify interactions whose provider state match the provided regular expression. An empty string matches interactions that have no state| |:pact.verifier.publishResults|Publishing of verification results will be skipped unless this property is set to 'true'| -|:pact.matching.wildcard|Enables matching of map values ignoring the keys when this property is set to 'true'| Example, to run verification only for a particular consumer: diff --git a/provider/maven/README.md b/provider/maven/README.md index 0349add83d..1ff423c9c6 100644 --- a/provider/maven/README.md +++ b/provider/maven/README.md @@ -262,7 +262,6 @@ The following plugin properties can be specified with `-Dproperty=value` on the |`pact.filter.providerState`|Only verify interactions whose provider state match the provided regular expression. An empty string matches interactions that have no state| |`pact.filter.pacturl`|This filter allows just the just the changed pact specified in a webhook to be run. It should be used in conjunction with `pact.filter.consumers`| |`pact.verifier.publishResults`|Publishing of verification results will be skipped unless this property is set to `true` [version 3.5.18+]| -|`pact.matching.wildcard`|Enables matching of map values ignoring the keys when this property is set to `true`| |`pact.verifier.disableUrlPathDecoding`|Disables decoding of request paths| |`pact.pactbroker.httpclient.usePreemptiveAuthentication`|Enables preemptive authentication with the pact broker when set to `true`| |`pact.consumer.tags`|Overrides the tags used when publishing pacts [version 4.0.7+]|