From 808684fd7239d1fe519c2ebc0f137af1d0006932 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 4 May 2017 15:15:10 +0200 Subject: [PATCH] Introduction of "stubs per consumer" approach There are cases in which 2 consumers of the same endpoint want to have 2 different responses. Without this change we don't support it. We don't support that consumers may overlap with their contracts. The outcome of such overlapping is that the first matching stub will be registered in HTTP server stub and the other will be ignored. With this change we're allowing such a thing to happen. Each consumer can set the `stubrunner.stubs-per-consumer` flag and the `spring.application.name` or `stubrunner.consumer-name` flag will be taken into consideration. If the consumer's name is present in the path of a stub / contract then its mapping / messaging contract will be reused in tests. If not then it will get ignored fixes #224 --- spring-cloud-contract-stub-runner/README.adoc | 95 +++++++++++++++++- .../contract/stubrunner/StubRepository.java | 16 ++- .../cloud/contract/stubrunner/StubRunner.java | 2 +- .../stubrunner/StubRunnerOptions.java | 35 ++++++- .../stubrunner/StubRunnerOptionsBuilder.java | 14 ++- .../stubrunner/junit/StubRunnerRule.java | 20 +++- .../spring/AutoConfigureStubRunner.java | 29 ++++++ .../spring/StubRunnerConfiguration.java | 12 ++- .../spring/StubRunnerProperties.java | 27 +++++ .../stubrunner/StubRepositorySpec.groovy | 19 +++- .../stubrunner/StubRunnerExecutorSpec.groovy | 8 +- .../contract/stubrunner/StubServerSpec.groovy | 6 +- .../StubRunnerStubsPerConsumerSpec.groovy | 91 +++++++++++++++++ ...tubsPerConsumerWithConsumerNameSpec.groovy | 92 +++++++++++++++++ ...MultipleConsumers-0.0.1-SNAPSHOT-stubs.jar | Bin 0 -> 3633 bytes ...erWithMultipleConsumers-0.0.1-SNAPSHOT.pom | 25 +++++ .../maven-metadata.xml | 28 ++++++ 17 files changed, 501 insertions(+), 18 deletions(-) create mode 100644 spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerSpec.groovy create mode 100644 spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerWithConsumerNameSpec.groovy create mode 100644 spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/0.0.1-SNAPSHOT/producerWithMultipleConsumers-0.0.1-SNAPSHOT-stubs.jar create mode 100644 spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/0.0.1-SNAPSHOT/producerWithMultipleConsumers-0.0.1-SNAPSHOT.pom create mode 100644 spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/maven-metadata.xml diff --git a/spring-cloud-contract-stub-runner/README.adoc b/spring-cloud-contract-stub-runner/README.adoc index 912a71101d..6e77e85ce0 100644 --- a/spring-cloud-contract-stub-runner/README.adoc +++ b/spring-cloud-contract-stub-runner/README.adoc @@ -197,7 +197,6 @@ for every registered WireMock server. Example for Stub Runner ids Which you can reference in your code. - === Stub Runner Spring Cloud Stub Runner can integrate with Spring Cloud. @@ -340,3 +339,97 @@ That way your deployed application can send requests to started WireMock servers discovery. Most likely points 1-3 could be set by default in `application.yml` cause they are not likely to change. That way you can provide only the list of stubs to download whenever you start the Stub Runner Boot. + +=== Stubs Per Consumer + +There are cases in which 2 consumers of the same endpoint want to have 2 different responses. + +TIP: This approach also allows you to immediately know which consumer is using which part of your API. +You can remove part of a response that your API produces and you can see which of your autogenerated tests +fails. If none fails then you can safely delete that part of the response cause nobody is using it. + +Let's look at the following example for contract defined for the producer called `producer`. +There are 2 consumers: `foo-consumer` and `bar-consumer`. + +*Consumer `foo-service`* + +[source,groovy] +---- +request { + url '/foo' + method GET() +} +response { + status 200 + body( + foo: "foo" + } +} +---- + +*Consumer `bar-service`* + +[source,groovy] +---- +request { + url '/foo' + method GET() +} +response { + status 200 + body( + bar: "bar" + } +} +---- + +You can't produce for the same request 2 different responses. That's why you can properly package the +contracts and then profit from the `stubsPerConsumer` feature. + +On the producer side the consumers can have a folder that contains contracts related only to them. +By setting the `stubrunner.stubs-per-consumer` flag to `true` we no longer register all stubs but only those that +correspond to the consumer application's name. In other words we'll scan the path of every stub and +if it contains the subfolder with name of the consumer in the path only then will it get registered. + +On the `foo` producer side the contracts would look like this + +[source,bash] +---- +. +└── contracts + ├── bar-consumer + │   ├── bookReturnedForBar.groovy + │   └── shouldCallBar.groovy + └── foo-consumer + ├── bookReturnedForFoo.groovy + └── shouldCallFoo.groovy +---- + +Being the `bar-consumer` consumer you can either set the `spring.application.name` or the `stubrunner.consumer-name` to `bar-consumer` +Or set the test as follows: + +[source,groovy] +---- +include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerSpec.groovy[tags=test] +... +} +---- + +Then only the stubs registered under a path that contains the `bar-consumer` in its name (i.e. those from the +`src/test/resources/contracts/bar-consumer/some/contracts/...` folder) will be allowed to be referenced. + +Or set the consumer name explicitly + +[source,groovy] +---- +include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerWithConsumerNameSpec.groovy[tags=test] +... +} +---- + +Then only the stubs registered under a path that contains the `foo-consumer` in its name (i.e. those from the +`src/test/resources/contracts/foo-consumer/some/contracts/...` folder) will be allowed to be referenced. + +You can check out https://github.com/spring-cloud/spring-cloud-contract/issues/224[issue 224] for more +information about the reasons behind this change. + diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRepository.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRepository.java index 5c8aaf242f..0e35602c68 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRepository.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRepository.java @@ -44,13 +44,15 @@ class StubRepository { private final File path; final List projectDescriptors; final Collection contracts; + private final StubRunnerOptions options; - public StubRepository(File repository) { + public StubRepository(File repository, StubRunnerOptions options) { if (!repository.isDirectory()) { throw new IllegalArgumentException( "Missing descriptor repository under path [" + repository + "]"); } this.path = repository; + this.options = options; this.projectDescriptors = projectDescriptors(); this.contracts = contracts(); } @@ -101,7 +103,7 @@ class StubRepository { public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { File file = path.toFile(); - if (isMappingDescriptor(file)) { + if (isMappingDescriptor(file) && isStubPerConsumerPathMatching(file)) { mappingDescriptors .add(new WiremockMappingDescriptor(file)); } @@ -129,7 +131,7 @@ class StubRepository { public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { File file = path.toFile(); - if (isContractDescriptor(file)) { + if (isContractDescriptor(file) && isStubPerConsumerPathMatching(file)) { mappingDescriptors .add(ContractVerifierDslConverter.convert(file)); } @@ -147,6 +149,14 @@ class StubRepository { return file.isFile() && file.getName().endsWith(".json"); } + private boolean isStubPerConsumerPathMatching(File file) { + if (!this.options.isStubsPerConsumer()) { + return true; + } + String consumerName = this.options.getConsumerName(); + return file.getAbsolutePath().contains(File.separator + consumerName + File.separator); + } + private static boolean isContractDescriptor(File file) { // TODO: Consider script injections implications... return file.isFile() && file.getName().endsWith(".groovy"); diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunner.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunner.java index 77f68c5c15..f983452955 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunner.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunner.java @@ -55,7 +55,7 @@ public class StubRunner implements StubRunning { MessageVerifier contractVerifierMessaging) { this.stubsConfiguration = stubsConfiguration; this.stubRunnerOptions = stubRunnerOptions; - this.stubRepository = new StubRepository(new File(repositoryPath)); + this.stubRepository = new StubRepository(new File(repositoryPath), this.stubRunnerOptions); AvailablePortScanner portScanner = new AvailablePortScanner( stubRunnerOptions.getMinPortValue(), stubRunnerOptions.getMaxPortValue()); this.localStubRunner = new StubRunnerExecutor(portScanner, contractVerifierMessaging); diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerOptions.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerOptions.java index ccd7de9fb2..ec18e974f8 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerOptions.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerOptions.java @@ -75,11 +75,22 @@ public class StubRunnerOptions { */ private final StubRunnerProxyOptions stubRunnerProxyOptions; + /** + * Should only stubs applicable for the given consumer get registered + */ + private boolean stubsPerConsumer = false; + + /** + * Name of the consumer. If not set should default to {@code spring.application.name} + */ + private String consumerName; + StubRunnerOptions(Integer minPortValue, Integer maxPortValue, String stubRepositoryRoot, boolean workOffline, String stubsClassifier, Collection dependencies, Map stubIdsToPortMapping, - String username, String password, final StubRunnerProxyOptions stubRunnerProxyOptions) { + String username, String password, final StubRunnerProxyOptions stubRunnerProxyOptions, + boolean stubsPerConsumer, String consumerName) { this.minPortValue = minPortValue; this.maxPortValue = maxPortValue; this.stubRepositoryRoot = stubRepositoryRoot; @@ -90,6 +101,8 @@ public class StubRunnerOptions { this.username = username; this.password = password; this.stubRunnerProxyOptions = stubRunnerProxyOptions; + this.stubsPerConsumer = stubsPerConsumer; + this.consumerName = consumerName; } public Integer port(StubConfiguration stubConfiguration) { @@ -121,6 +134,22 @@ public class StubRunnerOptions { return this.stubRunnerProxyOptions; } + public boolean isStubsPerConsumer() { + return this.stubsPerConsumer; + } + + public void setStubsPerConsumer(boolean stubsPerConsumer) { + this.stubsPerConsumer = stubsPerConsumer; + } + + public String getConsumerName() { + return this.consumerName; + } + + public void setConsumerName(String consumerName) { + this.consumerName = consumerName; + } + public static class StubRunnerProxyOptions { private final String proxyHost; @@ -151,7 +180,9 @@ public class StubRunnerOptions { + ", workOffline=" + this.workOffline + ", stubsClassifier='" + this.stubsClassifier + '\'' + ", dependencies=" + this.dependencies + ", stubIdsToPortMapping=" + this.stubIdsToPortMapping + ", username='" + this.username + '\'' + ", password='" - + this.password + '\'' + ", stubRunnerProxyOptions=" + this.stubRunnerProxyOptions + + this.password + '\'' + ", stubRunnerProxyOptions='" + this.stubRunnerProxyOptions + "', stubsPerConsumer='" + + this.stubsPerConsumer + + '\'' + ", stubsPerConsumer='" + this.stubsPerConsumer + '\'' + '}'; } } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerOptionsBuilder.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerOptionsBuilder.java index 01ed1c61e1..53fc990c5e 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerOptionsBuilder.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerOptionsBuilder.java @@ -41,6 +41,8 @@ public class StubRunnerOptionsBuilder { private String username; private String password; private StubRunnerOptions.StubRunnerProxyOptions stubRunnerProxyOptions; + private boolean stubPerConsumer = false; + private String consumerName; public StubRunnerOptionsBuilder() { } @@ -118,7 +120,7 @@ public class StubRunnerOptionsBuilder { public StubRunnerOptions build() { return new StubRunnerOptions(this.minPortValue, this.maxPortValue, this.stubRepositoryRoot, this.workOffline, this.stubsClassifier, buildDependencies(), this.stubIdsToPortMapping, - this.username, this.password, this.stubRunnerProxyOptions); + this.username, this.password, this.stubRunnerProxyOptions, this.stubPerConsumer, this.consumerName); } private Collection buildDependencies() { @@ -193,4 +195,14 @@ public class StubRunnerOptionsBuilder { this.stubRunnerProxyOptions = new StubRunnerOptions.StubRunnerProxyOptions(proxyHost, proxyPort); return this; } + + public StubRunnerOptionsBuilder withStubPerConsumer(boolean stubPerConsumer) { + this.stubPerConsumer = stubPerConsumer; + return this; + } + + public StubRunnerOptionsBuilder withConsumerName(String consumerName) { + this.consumerName = consumerName; + return this; + } } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRule.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRule.java index bd7986bee7..b4346c6d7a 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRule.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRule.java @@ -77,7 +77,9 @@ public class StubRunnerRule implements TestRule, StubFinder { .withStubsClassifier(System.getProperty("stubrunner.classifier", "stubs")) .withStubs(System.getProperty("stubrunner.ids", "")) .withUsername(System.getProperty("stubrunner.username")) - .withPassword(System.getProperty("stubrunner.password")); + .withPassword(System.getProperty("stubrunner.password")) + .withStubPerConsumer(Boolean.parseBoolean(System.getProperty("stubrunner.stubsPerConsumer", "false"))) + .withConsumerName(System.getProperty("stubrunner.consumer-name")); String proxyHost = System.getProperty("stubrunner.proxy.host"); if (proxyHost != null) { builder.withProxy(proxyHost, Integer.parseInt(System.getProperty("stubrunner.proxy.port"))); @@ -201,6 +203,22 @@ public class StubRunnerRule implements TestRule, StubFinder { return this; } + /** + * Allows stub per consumer + */ + public StubRunnerRule withStubPerConsumer(boolean stubPerConsumer) { + this.stubRunnerOptionsBuilder.withStubPerConsumer(stubPerConsumer); + return this; + } + + /** + * Allows setting consumer name + */ + public StubRunnerRule withConsumerName(String consumerName) { + this.stubRunnerOptionsBuilder.withConsumerName(consumerName); + return this; + } + @Override public URL findStubUrl(String groupId, String artifactId) { return this.stubFinder.findStubUrl(groupId, artifactId); diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/AutoConfigureStubRunner.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/AutoConfigureStubRunner.java index 96d0ba065c..926f703e69 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/AutoConfigureStubRunner.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/AutoConfigureStubRunner.java @@ -69,4 +69,33 @@ public @interface AutoConfigureStubRunner { * The classifier to use by default in ivy co-ordinates for a stub. */ String classifier() default "stubs"; + + /** + * On the producer side the consumers can have a folder that contains contracts related only to them. By setting the flag to {@code true} + * we no longer register all stubs but only those that correspond to the consumer application's name. In other words + * we'll scan the path of every stub and if it contains the name of the consumer in the path only then will it get registered. + * + * Let's look at this example. Let's assume + * that we have a producer called {@code foo} and two consumers {@code baz} and {@code bar}. On the {@code foo} producer side the + * contracts would look like this + * {@code src/test/resources/contracts/baz-service/some/contracts/...} and + * {@code src/test/resources/contracts/bar-service/some/contracts/...}. + * + * Then when the consumer with {@code spring.application.name} or the {@link AutoConfigureStubRunner#consumerName()} + * annotation parameter set to {@code baz-service} will define the test setup as follows + * {@code @AutoConfigureStubRunner(ids = "com.example:foo:+:stubs:8095", stubsPerConsumer=true)} then only the stubs registered + * under {@code src/test/resources/contracts/baz-service/some/contracts/...} will get registered and those under + * {@code src/test/resources/contracts/bar-service/some/contracts/...} will get ignored. + * + * @see issue 224 + * + */ + boolean stubsPerConsumer() default false; + + /** + * You can override the default {@code spring.application.name} of this field by setting a value to this parameter. + * + * @see issue 224 + */ + String consumerName() default ""; } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfiguration.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfiguration.java index eb7e9a8ec7..7e7a7c9eda 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfiguration.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.cloud.contract.stubrunner.StubConfiguration; import org.springframework.cloud.contract.stubrunner.StubDownloader; import org.springframework.cloud.contract.stubrunner.StubRunnerOptions; import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder; +import org.springframework.cloud.contract.stubrunner.util.StringUtils; import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; import org.springframework.cloud.contract.verifier.messaging.noop.NoOpStubMessages; import org.springframework.context.annotation.Bean; @@ -92,7 +93,16 @@ public class StubRunnerConfiguration { .withStubsClassifier(this.props.getClassifier()) .withStubs(this.props.getIds()) .withUsername(this.props.getUsername()) - .withPassword(this.props.getPassword()); + .withPassword(this.props.getPassword()) + .withStubPerConsumer(this.props.isStubsPerConsumer()) + .withConsumerName(consumerName()); + } + + private String consumerName() { + if (StringUtils.hasText(this.props.getConsumerName())) { + return this.props.getConsumerName(); + } + return this.environment.getProperty("spring.application.name"); } private String uriStringOrEmpty(Resource stubRepositoryRoot) throws IOException { diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerProperties.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerProperties.java index 28dbb35607..3850751a24 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerProperties.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerProperties.java @@ -85,6 +85,16 @@ public class StubRunnerProperties { */ private String proxyHost; + /** + * Should only stubs for this particular consumer get registered in HTTP server stub. + */ + private boolean stubsPerConsumer; + + /** + * You can override the default {@code spring.application.name} of this field by setting a value to this parameter. + */ + private String consumerName; + public int getMinPort() { return this.minPort; } @@ -173,10 +183,27 @@ public class StubRunnerProperties { this.contextPath = contextPath; } + public boolean isStubsPerConsumer() { + return this.stubsPerConsumer; + } + + public void setStubsPerConsumer(boolean stubsPerConsumer) { + this.stubsPerConsumer = stubsPerConsumer; + } + + public String getConsumerName() { + return this.consumerName; + } + + public void setConsumerName(String consumerName) { + this.consumerName = consumerName; + } + @Override public String toString() { return "StubRunnerProperties{" + "minPort=" + this.minPort + ", maxPort=" + this.maxPort + ", workOffline=" + this.workOffline + ", repositoryRoot=" + this.repositoryRoot + ", ids=" + Arrays.toString(this.ids) + ", classifier='" + this.classifier + '\'' + + ", setStubsPerConsumer='" + this.stubsPerConsumer + "', consumerName='" + this.consumerName + '\'' + '}'; } } diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRepositorySpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRepositorySpec.groovy index 9a772f7cb7..8bdacc4ecc 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRepositorySpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRepositorySpec.groovy @@ -24,7 +24,7 @@ class StubRepositorySpec extends Specification { def 'should retrieve all descriptors for given project'() { given: - StubRepository repository = new StubRepository(REPOSITORY_LOCATION) + StubRepository repository = new StubRepository(REPOSITORY_LOCATION, new StubRunnerOptionsBuilder().build()) int expectedDescriptorsSize = 8 when: List descriptors = repository.getProjectDescriptors() @@ -34,7 +34,7 @@ class StubRepositorySpec extends Specification { def 'should return empty list if files are missing'() { given: - StubRepository repository = new StubRepository(new File('src/test/resources/emptyrepo')) + StubRepository repository = new StubRepository(new File('src/test/resources/emptyrepo'), new StubRunnerOptionsBuilder().build()) when: List descriptors = repository.getProjectDescriptors() then: @@ -43,8 +43,21 @@ class StubRepositorySpec extends Specification { def 'should throw an exception if directory with mappings is missing'() { when: - new StubRepository(new File('src/test/resources/nonexistingrepo')) + new StubRepository(new File('src/test/resources/nonexistingrepo'), new StubRunnerOptionsBuilder().build()) then: thrown(IllegalArgumentException) } + + def 'should retrieve only those mappings that contain the consumer name'() { + given: + StubRepository repository = new StubRepository(REPOSITORY_LOCATION, + new StubRunnerOptionsBuilder() + .withStubPerConsumer(true) + .withConsumerName("ping").build()) + int expectedDescriptorsSize = 1 + when: + List descriptors = repository.getProjectDescriptors() + then: + descriptors.size() == expectedDescriptorsSize + } } diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerExecutorSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerExecutorSpec.groovy index 466ac0e531..4510513268 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerExecutorSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerExecutorSpec.groovy @@ -38,7 +38,7 @@ class StubRunnerExecutorSpec extends Specification { def setup() { portScanner = new AvailablePortScanner(MIN_PORT, MAX_PORT) - repository = new StubRepository(new File('src/test/resources/repository')) + repository = new StubRepository(new File('src/test/resources/repository'), new StubRunnerOptionsBuilder().build()) } def 'should provide URL for given relative path of stub'() { @@ -122,7 +122,8 @@ class StubRunnerExecutorSpec extends Specification { StubRunnerExecutor executor = new StubRunnerExecutor(portScanner) when: executor.runStubs(stubRunnerOptions, - new StubRepository(new File('src/test/resources/repository/httpcontract')), stubConf) + new StubRepository(new File('src/test/resources/repository/httpcontract'), + new StubRunnerOptionsBuilder().build()), stubConf) then: !executor.trigger() !executor.trigger("missing", "label") @@ -137,7 +138,8 @@ class StubRunnerExecutorSpec extends Specification { StubRunnerExecutor executor = new StubRunnerExecutor(portScanner) when: RunningStubs stubs = executor.runStubs(stubRunnerOptions, - new StubRepository(new File('src/test/resources/emptyrepo')), stubConf) + new StubRepository(new File('src/test/resources/emptyrepo'), + new StubRunnerOptionsBuilder().build()), stubConf) then: stubs.getPort('asd') == -1 cleanup: diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubServerSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubServerSpec.groovy index ac86799432..56f1a518ef 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubServerSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubServerSpec.groovy @@ -27,7 +27,8 @@ class StubServerSpec extends Specification { def 'should register stub mappings upon server start'() { given: - List mappingDescriptors = new StubRepository(repository).getProjectDescriptors() + List mappingDescriptors = new StubRepository(repository, + new StubRunnerOptionsBuilder().build()).getProjectDescriptors() StubServer pingStubServer = new StubServer(stubConfiguration, mappingDescriptors, [], new WireMockHttpServerStub(STUB_SERVER_PORT)) when: @@ -39,7 +40,8 @@ class StubServerSpec extends Specification { def 'should provide stub server URL'() { given: - List mappingDescriptors = new StubRepository(repository).getProjectDescriptors() + List mappingDescriptors = new StubRepository(repository, + new StubRunnerOptionsBuilder().build()).getProjectDescriptors() StubServer pingStubServer = new StubServer(stubConfiguration, mappingDescriptors, [], new WireMockHttpServerStub(STUB_SERVER_PORT)) when: diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerSpec.groovy new file mode 100644 index 0000000000..c51b1496d8 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerSpec.groovy @@ -0,0 +1,91 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.stubrunner.spring.cloud + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootContextLoader +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.cloud.contract.stubrunner.StubFinder +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner +import org.springframework.cloud.contract.verifier.messaging.MessageVerifier +import org.springframework.cloud.stream.annotation.EnableBinding +import org.springframework.cloud.stream.messaging.Sink +import org.springframework.context.annotation.Configuration +import org.springframework.core.env.Environment +import org.springframework.http.ResponseEntity +import org.springframework.messaging.Message +import org.springframework.test.annotation.DirtiesContext +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +/** + * @author Marcin Grzejszczak + */ +// tag::test[] +@ContextConfiguration(classes = Config, loader = SpringBootContextLoader) +@SpringBootTest(properties = ["spring.application.name=bar-consumer"]) +@AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers", + repositoryRoot = "classpath:m2repo/repository/", + stubsPerConsumer = true) +@DirtiesContext +class StubRunnerStubsPerConsumerSpec extends Specification { +// end::test[] + + @Autowired StubFinder stubFinder + @Autowired Environment environment + @Autowired MessageVerifier> messaging + TestRestTemplate template = new TestRestTemplate() + + def 'should start http stub servers for bar-consumer only'() { + given: + URL stubUrl = stubFinder.findStubUrl('producerWithMultipleConsumers') + when: + ResponseEntity entity = template.getForEntity("${stubUrl}/bar-consumer", String) + then: + entity.statusCode.value() == 200 + when: + entity = template.getForEntity("${stubUrl}/foo-consumer", String) + then: + entity.statusCode.value() == 404 + } + + def 'should trigger a message by label from proper consumer'() { + when: + stubFinder.trigger('return_book_for_bar') + then: + Message receivedMessage = messaging.receive('output') + and: + receivedMessage != null + receivedMessage.payload == '''{"bookName":"foo_for_bar"}''' + receivedMessage.headers.get('BOOK-NAME') == 'foo_for_bar' + } + + def 'should not trigger a message by the not matching consumer'() { + when: + stubFinder.trigger('return_book_for_foo') + then: + IllegalArgumentException e = thrown(IllegalArgumentException) + e.message.contains("No label with name [return_book_for_foo] was found") + } + + @Configuration + @EnableAutoConfiguration + @EnableBinding(Sink) + static class Config {} +} \ No newline at end of file diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerWithConsumerNameSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerWithConsumerNameSpec.groovy new file mode 100644 index 0000000000..8e451ff9e6 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerWithConsumerNameSpec.groovy @@ -0,0 +1,92 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.stubrunner.spring.cloud + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootContextLoader +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.cloud.contract.stubrunner.StubFinder +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner +import org.springframework.cloud.contract.verifier.messaging.MessageVerifier +import org.springframework.cloud.stream.annotation.EnableBinding +import org.springframework.cloud.stream.messaging.Sink +import org.springframework.context.annotation.Configuration +import org.springframework.core.env.Environment +import org.springframework.http.ResponseEntity +import org.springframework.messaging.Message +import org.springframework.test.annotation.DirtiesContext +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +/** + * @author Marcin Grzejszczak + */ +// tag::test[] +@ContextConfiguration(classes = Config, loader = SpringBootContextLoader) +@SpringBootTest +@AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers", + repositoryRoot = "classpath:m2repo/repository/", + consumerName = "foo-consumer", + stubsPerConsumer = true) +@DirtiesContext +class StubRunnerStubsPerConsumerWithConsumerNameSpec extends Specification { +// end::test[] + + @Autowired StubFinder stubFinder + @Autowired Environment environment + @Autowired MessageVerifier> messaging + TestRestTemplate template = new TestRestTemplate() + + def 'should start http stub servers for foo-consumer only'() { + given: + URL stubUrl = stubFinder.findStubUrl('producerWithMultipleConsumers') + when: + ResponseEntity entity = template.getForEntity("${stubUrl}/foo-consumer", String) + then: + entity.statusCode.value() == 200 + when: + entity = template.getForEntity("${stubUrl}/bar-consumer", String) + then: + entity.statusCode.value() == 404 + } + + def 'should trigger a message by label from proper consumer'() { + when: + stubFinder.trigger('return_book_for_foo') + then: + Message receivedMessage = messaging.receive('output') + and: + receivedMessage != null + receivedMessage.payload == '''{"bookName":"foo_for_foo"}''' + receivedMessage.headers.get('BOOK-NAME') == 'foo_for_foo' + } + + def 'should not trigger a message by the not matching consumer'() { + when: + stubFinder.trigger('return_book_for_bar') + then: + IllegalArgumentException e = thrown(IllegalArgumentException) + e.message.contains("No label with name [return_book_for_bar] was found") + } + + @Configuration + @EnableAutoConfiguration + @EnableBinding(Sink) + static class Config {} +} \ No newline at end of file diff --git a/spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/0.0.1-SNAPSHOT/producerWithMultipleConsumers-0.0.1-SNAPSHOT-stubs.jar b/spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/0.0.1-SNAPSHOT/producerWithMultipleConsumers-0.0.1-SNAPSHOT-stubs.jar new file mode 100644 index 0000000000000000000000000000000000000000..73554a760b9826f3fefa11411160a3b7a5ac1285 GIT binary patch literal 3633 zcmWIWW@h1HVBp|j&|{wF!2kqIAOZ+Df!NnI#8KDN&rP41Apk|;rh2A#(m(~0KrDi+ z(AUw=)6F$FM9TRCpkZ_q$n}Dq*y;R zgp+~U+le_6gi9;985mh!Ff%ZK38>eBhM*g#gv+?J{Cr)Y;l-u7sYL|M26=>ofwyRh zSAAU2*UccuGBPk&6J>T%etvdPYDsBPUTTV4evw;#zFvA!etuac*h4d=xg+a=7|lbW zey91G3P-yU`5m_3rtTx+I^>`IAF>UelRH&EC%$7pip8-VnZqZIeIGlUXx0OE*QV$9nODeAOEfy>E5&5u4-1@?q<@~8nTXetuoS6SFSaD;E4k4{Hrtbt;F1!HNKJ5{ zfL!~k`zyhKL@^r?kWPt3$Z3tFfRwn-GG!U(Hf6pQ_3DnVa?8`pxf5C(_&zV`zjMd+Mci+uZo2C28 zA27m8=mau;zv4(&S#%3Ut!pK9;es&ElSuke9(~0n-+HF`=H?|r)zFUS#q9lXKal z+y6s6bP|t!VnQT2-D=b#QxP?P!HZeUBJLQl=mcT3(gtC)K9NSF!~wVA`nKiZ1uCCfB-EoT^(Oty}+Oi zK`YokH7YH2wN#ilsV*pKVqQ^QTw!Kj*2GDCE=J~CRg~>nb>6>|TErr<=4zuva12Ap ztgaNlLeC`6OhbZLqMtIr>nIH%9GZ3J+2}Btrz~Uf3A8@=1WFuLP z+~8n^G&s=W2%phPC`PLQ8wpsAMoA%|A^7ZpBt6Wg2gD0N3m6(28;G(8t*8VgM_5Ay z=m1bd1gl38r88=sj_iOHz*r^hQPiw}WH;6p9q#x;4dki7Mj%$ZQ3?`b0vWTqMfT%I zqU=HV5xFWN9Kj&FQENYByYI0O<43dzCe{IvOoCadKpX(cBphrg4uEGf%!G`!dO^e` zW(9+6O)?H^FryM{RRgjHS7n17ocoBe1~n!j1vfAru~a*d0s&VYfoyRDI}yQ%uozKe b04>H+TLgHs0)4{3zz>8sSs56dI6yoAq0b^u literal 0 HcmV?d00001 diff --git a/spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/0.0.1-SNAPSHOT/producerWithMultipleConsumers-0.0.1-SNAPSHOT.pom b/spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/0.0.1-SNAPSHOT/producerWithMultipleConsumers-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..b0d40ceaf6 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/0.0.1-SNAPSHOT/producerWithMultipleConsumers-0.0.1-SNAPSHOT.pom @@ -0,0 +1,25 @@ + + + + + 4.0.0 + org.springframework.cloud.contract.verifier.stubs + producerWithMultipleConsumers + 0.0.1-SNAPSHOT + pom + diff --git a/spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/maven-metadata.xml b/spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/maven-metadata.xml new file mode 100644 index 0000000000..1079684277 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/producerWithMultipleConsumers/maven-metadata.xml @@ -0,0 +1,28 @@ + + + + + org.springframework.cloud.contract.verifier.stubs + producerWithMultipleConsumers + 0.0.1-SNAPSHOT + + + 0.0.1-SNAPSHOT + + 20160409062112 + +