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 0000000000..73554a760b Binary files /dev/null and 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 differ 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 + +