From b48e37daf6d3171fec7c402ad8fc65dcfd21e54a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Gonz=C3=A1lez=20Ib=C3=A1=C3=B1ez?= Date: Sun, 9 Jan 2022 11:50:44 +0100 Subject: [PATCH] feat: Add support for PactLoader path value expressions --- .../junit/loader/PactFolderLoaderSpec.groovy | 169 ++++++++++++++++++ .../junit/loader/PactFolderLoaderTest.groovy | 46 ----- .../junitsupport/loader/PactFolder.java | 9 +- .../junitsupport/loader/PactFolderLoader.kt | 54 +++++- 4 files changed, 227 insertions(+), 51 deletions(-) create mode 100644 provider/junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactFolderLoaderSpec.groovy delete mode 100644 provider/junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactFolderLoaderTest.groovy diff --git a/provider/junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactFolderLoaderSpec.groovy b/provider/junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactFolderLoaderSpec.groovy new file mode 100644 index 0000000000..5510796d25 --- /dev/null +++ b/provider/junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactFolderLoaderSpec.groovy @@ -0,0 +1,169 @@ +package au.com.dius.pact.provider.junit.loader + +import au.com.dius.pact.core.support.expressions.ValueResolver +import au.com.dius.pact.provider.junitsupport.loader.PactFolder +import au.com.dius.pact.provider.junitsupport.loader.PactFolderLoader +import kotlin.jvm.JvmClassMappingKt +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable +import spock.lang.Specification +import spock.util.environment.RestoreSystemProperties + +class PactFolderLoaderSpec extends Specification { + + def 'handles the case where the configured directory does not exist'() { + given: + def file = new File('/does/not/exist') + + when: + def result = new PactFolderLoader(file).load('provider') + + then: + result == [] + } + + def 'only includes json files'() { + given: + PactFolder annotation = ParticularFolderPactLoaderAnnotation.class.getAnnotation(PactFolder) + + when: + def result = new PactFolderLoader(annotation).load('myAwesomeService') + + then: + result.size() == 3 + } + + def 'only includes json files that match the provider name'() { + given: + PactFolder annotation = ParticularFolderPactLoaderAnnotation.class.getAnnotation(PactFolder) + + when: + def result = new PactFolderLoader(annotation).load('myAwesomeService2') + + then: + result.size() == 1 + } + + def 'is able to load files from a directory'() { + given: + File tmpDir = File.createTempDir() + tmpDir.deleteOnExit() + File pactFile = new File(tmpDir, 'pact.json') + pactFile.deleteOnExit() + pactFile.text = this.class.classLoader.getResourceAsStream('pacts/contract.json').text + + when: + def result = new PactFolderLoader(tmpDir.path).load('myAwesomeService') + + then: + result.size() == 1 + } + + def 'is able to load files from a directory with spaces in the path'() { + given: + def dirWithSpaces = 'dir with spaces!' + + when: + def result = new PactFolderLoader(dirWithSpaces).load('myAwesomeService') + + then: + result.size() == 1 + } + + @RestoreSystemProperties + def "resolves path using default resolver (SystemPropertyResolver)"() { + given: + def exprPath = 'pact${valueToBeResolved}' + System.setProperty('valueToBeResolved', "s") + + when: + def result = new PactFolderLoader(exprPath).load('myAwesomeService') + + then: + result.size() == 3 + } + + def "resolves path using given resolver"() { + given: + def exprPath = 'pact${valueToBeResolved}' + def valueResolver = [resolveValue: { val -> 's' }] as ValueResolver + + when: + def result = new PactFolderLoader(exprPath, null, valueResolver).load('myAwesomeService') + + then: + result.size() == 3 + } + + def "resolves path using given resolver class"() { + given: + def exprPath = 'pact${valueToBeResolved}' + def constantValueResolver = JvmClassMappingKt.getKotlinClass(ConstantValueResolver.class) + + when: + def result = new PactFolderLoader(exprPath, constantValueResolver).load('myAwesomeService') + + then: + result.size() == 3 + } + + @RestoreSystemProperties + def "resolves path using minimal annotation (resolver SystemPropertyResolver)"() { + given: + System.setProperty('pactfolder.path', "pacts") + def annotation = MinimalPactLoaderAnnotation.class.getAnnotation(PactFolder.class) + + when: + def result = new PactFolderLoader(annotation).load('myAwesomeService') + + then: + result.size() == 3 + } + + @RestoreSystemProperties + def "resolves path using given revolver class via annotation"() { + given: + System.setProperty('pactfolder.path', "pacts") + def annotation = ParticularResolverPactLoaderAnnotation.class.getAnnotation(PactFolder.class) + + when: + def result = new PactFolderLoader(annotation).load('myAwesomeService') + + then: + result.size() == 3 + } + + @PactFolder + static class MinimalPactLoaderAnnotation { + + } + + @PactFolder('pacts') + static class ParticularFolderPactLoaderAnnotation { + + } + + @PactFolder(value = 'pact${valueToBeResolved}', valueResolver = ConstantValueResolver) + static class ParticularResolverPactLoaderAnnotation { + + } + + static class ConstantValueResolver implements ValueResolver { + + @Override + String resolveValue(@Nullable String property) { + return 's' + } + + @Override + String resolveValue(@Nullable String property, @Nullable String s) { + return 's' + } + + @Override + boolean propertyDefined(@NotNull String property) { + return true + } + } + +} diff --git a/provider/junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactFolderLoaderTest.groovy b/provider/junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactFolderLoaderTest.groovy deleted file mode 100644 index c89e856972..0000000000 --- a/provider/junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactFolderLoaderTest.groovy +++ /dev/null @@ -1,46 +0,0 @@ -package au.com.dius.pact.provider.junit.loader - -import au.com.dius.pact.provider.junitsupport.loader.PactFolder -import au.com.dius.pact.provider.junitsupport.loader.PactFolderLoader -import org.junit.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.empty -import static org.hamcrest.Matchers.hasSize -import static org.hamcrest.Matchers.is - -@PactFolder('pacts') -class PactFolderLoaderTest { - - @Test - void 'handles the case where the configured directory does not exist'() { - assertThat(new PactFolderLoader(new File('/does/not/exist')).load('provider'), is(empty())) - } - - @Test - void 'only includes json files'() { - assertThat(new PactFolderLoader(this.class.getAnnotation(PactFolder)).load('myAwesomeService'), hasSize(3)) - } - - @Test - void 'only includes json files that match the provider name'() { - assertThat(new PactFolderLoader(this.class.getAnnotation(PactFolder)).load('myAwesomeService2'), hasSize(1)) - } - - @Test - void 'is able to load files from a directory'() { - File tmpDir = File.createTempDir() - tmpDir.deleteOnExit() - File pactFile = new File(tmpDir, 'pact.json') - pactFile.deleteOnExit() - pactFile.text = this.class.classLoader.getResourceAsStream('pacts/contract.json').text - - assertThat(new PactFolderLoader(tmpDir.path).load('myAwesomeService'), hasSize(1)) - } - - @Test - void 'is able to load files from a directory with spaces in the path'() { - assert new PactFolderLoader('dir with spaces!').load('myAwesomeService').size() == 1 - } - -} diff --git a/provider/src/main/java/au/com/dius/pact/provider/junitsupport/loader/PactFolder.java b/provider/src/main/java/au/com/dius/pact/provider/junitsupport/loader/PactFolder.java index aac52c2088..0e21289fd2 100644 --- a/provider/src/main/java/au/com/dius/pact/provider/junitsupport/loader/PactFolder.java +++ b/provider/src/main/java/au/com/dius/pact/provider/junitsupport/loader/PactFolder.java @@ -1,5 +1,7 @@ package au.com.dius.pact.provider.junitsupport.loader; +import au.com.dius.pact.core.support.expressions.SystemPropertyResolver; +import au.com.dius.pact.core.support.expressions.ValueResolver; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; @@ -19,5 +21,10 @@ /** * @return path to subfolder of project resource folder with pact */ - String value(); + String value() default "${pactfolder.path:}"; + + /** + * Override the default value resolver for resolving the values in the expressions + */ + Class valueResolver() default SystemPropertyResolver.class; } diff --git a/provider/src/main/kotlin/au/com/dius/pact/provider/junitsupport/loader/PactFolderLoader.kt b/provider/src/main/kotlin/au/com/dius/pact/provider/junitsupport/loader/PactFolderLoader.kt index 05764a018b..ecd4990944 100644 --- a/provider/src/main/kotlin/au/com/dius/pact/provider/junitsupport/loader/PactFolderLoader.kt +++ b/provider/src/main/kotlin/au/com/dius/pact/provider/junitsupport/loader/PactFolderLoader.kt @@ -4,19 +4,44 @@ import au.com.dius.pact.core.model.DefaultPactReader import au.com.dius.pact.core.model.DirectorySource import au.com.dius.pact.core.model.Interaction import au.com.dius.pact.core.model.Pact +import au.com.dius.pact.core.support.expressions.DataType +import au.com.dius.pact.core.support.expressions.ExpressionParser +import au.com.dius.pact.core.support.expressions.SystemPropertyResolver +import au.com.dius.pact.core.support.expressions.ValueResolver import java.io.File import java.net.URLDecoder +import kotlin.reflect.KClass /** * Out-of-the-box implementation of [PactLoader] * that loads pacts from either a subfolder of project resource folder or a directory */ -class PactFolderLoader(private val path: File) : PactLoader where I : Interaction { - private val pactSource: DirectorySource = DirectorySource(path) +class PactFolderLoader : PactLoader where I : Interaction { - constructor(path: String) : this(File(path)) + private val path: File + private val pactSource: DirectorySource - constructor(pactFolder: PactFolder) : this(pactFolder.value) + @JvmOverloads + constructor( + path: String, + valueResolverClass: KClass? = null, + valueResolver: ValueResolver? = null + ) { + val resolver = setupValueResolver(valueResolver, valueResolverClass) + val interpolatedPath = ExpressionParser.parseExpression(path, DataType.STRING, resolver) as String + this.path = File(interpolatedPath) + this.pactSource = DirectorySource(this.path) + } + + constructor(pactFolder: PactFolder) : this( + pactFolder.value, + pactFolder.valueResolver + ) + + constructor(path: File) { + this.path = path + this.pactSource = DirectorySource(this.path) + } override fun description() = "Directory(${pactSource.dir})" @@ -46,4 +71,25 @@ class PactFolderLoader(private val path: File) : PactLoader where I : Interac return path } } + + private fun setupValueResolver( + valueResolver: ValueResolver?, + valueResolverClass: KClass? + ): ValueResolver { + var resolver: ValueResolver = valueResolver ?: SystemPropertyResolver + if (valueResolverClass != null) { + if (valueResolverClass.objectInstance != null) { + resolver = valueResolverClass.objectInstance!! + } else { + try { + resolver = valueResolverClass.java.newInstance() + } catch (e: InstantiationException) { + PactBrokerLoader.logger.warn(e) { "Failed to instantiate the value resolver, using the default" } + } catch (e: IllegalAccessException) { + PactBrokerLoader.logger.warn(e) { "Failed to instantiate the value resolver, using the default" } + } + } + } + return resolver + } }