From 56673bde4d83a7cdd1e6dadfc7a69af8517f4594 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 27 Jun 2024 14:08:33 +0200 Subject: [PATCH] Fails on no stubs for stubs Per Consumer without this change the JAR gets downloaded, it contains mappings but there are no mappings that are actually matching the given consumer. Regardless, the WireMock server starts with no mappings with this change an exception is being thrown in that case fixes gh-2114 --- .../contract/stubrunner/StubRepository.java | 11 ++- .../cloud/contract/stubrunner/StubRunner.java | 3 +- .../stubrunner/StubRepositorySpec.groovy | 16 ++-- .../stubrunner/StubRunnerExecutorSpec.groovy | 9 ++- .../stubrunner/StubRunnerFactorySpec.groovy | 17 +++++ ...sPerConsumerNotMatchingConsumerSpec.groovy | 73 +++++++++++++++++++ .../stubrunner/StubRepositoryTest.java | 2 +- 7 files changed, 114 insertions(+), 17 deletions(-) create mode 100644 spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerNotMatchingConsumerSpec.groovy 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 40595babc8..86647d8381 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 @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import io.micrometer.common.lang.Nullable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -57,7 +58,8 @@ class StubRepository { private final StubRunnerOptions options; - StubRepository(File repository, List httpServerStubs, StubRunnerOptions options) { + StubRepository(File repository, List httpServerStubs, StubRunnerOptions options, + @Nullable StubConfiguration stubConfiguration) { if (!repository.isDirectory()) { throw new IllegalArgumentException("Missing descriptor repository under path [" + repository + "]"); } @@ -70,13 +72,18 @@ class StubRepository { this.options = options; this.stubs = stubs(); this.contracts = contracts(); + if (options.isFailOnNoStubs() && this.stubs.isEmpty() && this.contracts.isEmpty()) { + throw new IllegalStateException("No stubs or contracts were found for [" + + (stubConfiguration != null ? stubConfiguration.toColonSeparatedDependencyNotation() : null) + + "] and the switch to fail on no stubs was set."); + } if (log.isTraceEnabled()) { log.trace("Found the following contracts " + this.contracts); } } StubRepository(File repository) { - this(repository, new ArrayList<>(), new StubRunnerOptionsBuilder().build()); + this(repository, new ArrayList<>(), new StubRunnerOptionsBuilder().build(), null); } public File getPath() { 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 54d3c4a5da..7a5a09399d 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 @@ -64,7 +64,8 @@ public class StubRunner implements StubRunning { this.stubsConfiguration = stubsConfiguration; this.stubRunnerOptions = stubRunnerOptions; List serverStubs = SpringFactoriesLoader.loadFactories(HttpServerStub.class, null); - this.stubRepository = new StubRepository(new File(repositoryPath), serverStubs, this.stubRunnerOptions); + this.stubRepository = new StubRepository(new File(repositoryPath), serverStubs, this.stubRunnerOptions, + stubsConfiguration); AvailablePortScanner portScanner = new AvailablePortScanner(stubRunnerOptions.getMinPortValue(), stubRunnerOptions.getMaxPortValue()); this.localStubRunner = new StubRunnerExecutor(portScanner, contractVerifierMessaging, serverStubs); 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 290eba00ab..c13e39ee1d 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 @@ -25,7 +25,7 @@ class StubRepositorySpec extends Specification { def 'should retrieve all descriptors for given project'() { given: StubRepository repository = new StubRepository(REPOSITORY_LOCATION, - [], new StubRunnerOptionsBuilder().build()) + [], new StubRunnerOptionsBuilder().build(), null) int expectedDescriptorsSize = 8 when: List descriptors = repository.getStubs() @@ -33,20 +33,18 @@ class StubRepositorySpec extends Specification { descriptors.size() == expectedDescriptorsSize } - def 'should return empty list if files are missing'() { - given: - StubRepository repository = new StubRepository(new File('src/test/resources/emptyrepo'), - [], new StubRunnerOptionsBuilder().build()) + def 'should throw an exception when no stubs or contracts are present'() { when: - List descriptors = repository.getStubs() + new StubRepository(new File('src/test/resources/emptyrepo'), + [], new StubRunnerOptionsBuilder().build(), null) then: - descriptors.empty + thrown(IllegalStateException) } def 'should throw an exception if directory with mappings is missing'() { when: new StubRepository(new File('src/test/resources/nonexistingrepo'), [], - new StubRunnerOptionsBuilder().build()) + new StubRunnerOptionsBuilder().build(), null) then: thrown(IllegalArgumentException) } @@ -56,7 +54,7 @@ class StubRepositorySpec extends Specification { StubRepository repository = new StubRepository(REPOSITORY_LOCATION, [], new StubRunnerOptionsBuilder() .withStubPerConsumer(true) - .withConsumerName("ping").build()) + .withConsumerName("ping").build(), null) int expectedDescriptorsSize = 1 when: List descriptors = repository.stubs 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 b76f97787b..d346964ae0 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 @@ -40,7 +40,7 @@ class StubRunnerExecutorSpec extends Specification { def setup() { portScanner = new AvailablePortScanner(MIN_PORT, MAX_PORT) repository = new StubRepository(new File('src/test/resources/repository'), - [], new StubRunnerOptionsBuilder().build()) + [], stubRunnerOptions, null) } def 'should provide URL for given relative path of stub'() { @@ -173,7 +173,7 @@ class StubRunnerExecutorSpec extends Specification { when: executor.runStubs(stubRunnerOptions, new StubRepository(new File('src/test/resources/repository/httpcontract'), - [], new StubRunnerOptionsBuilder().build()), stubConf) + [], new StubRunnerOptionsBuilder().build(), null), stubConf) then: !executor.trigger() !executor.trigger("missing", "label") @@ -186,10 +186,11 @@ class StubRunnerExecutorSpec extends Specification { given: def stubConf = new StubConfiguration('asd', 'asd', 'asd', '') StubRunnerExecutor executor = new StubRunnerExecutor(portScanner) + stubRunnerOptions = new StubRunnerOptionsBuilder().withFailOnNoStubs(false).build() when: RunningStubs stubs = executor.runStubs(stubRunnerOptions, new StubRepository(new File('src/test/resources/emptyrepo'), - [], new StubRunnerOptionsBuilder().build()), stubConf) + [], stubRunnerOptions, null), stubConf) then: stubs.getPort('asd') == -1 cleanup: @@ -204,7 +205,7 @@ class StubRunnerExecutorSpec extends Specification { def stubConf = new StubConfiguration('asd', 'asd', 'asd', '') executor.runStubs(stubRunnerOptions, new StubRepository(new File('src/test/resources/messages'), - [], new StubRunnerOptionsBuilder().build()), stubConf) + [], new StubRunnerOptionsBuilder().build(), null), stubConf) boolean triggered = executor.trigger("trigger") then: triggered diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerFactorySpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerFactorySpec.groovy index 46fa8a08b3..4f1b575ee7 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerFactorySpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerFactorySpec.groovy @@ -24,6 +24,22 @@ import org.springframework.cloud.contract.verifier.messaging.noop.NoOpStubMessag class StubRunnerFactorySpec extends Specification { + static final String MAPPING = ''' +{ + "request": { + "method": "GET", + "url": "/hello" + }, + "response": { + "status": 200, + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + } + } +} +''' + @Rule TemporaryFolder folder = new TemporaryFolder() @@ -42,6 +58,7 @@ class StubRunnerFactorySpec extends Specification { def "Should download stub definitions many times"() { given: folder.newFolder("mappings") + folder.newFile("hello.json").text = MAPPING 1 * downloader.downloadAndUnpackStubJar(_) >> new AbstractMap.SimpleEntry(new StubConfiguration('a:b'), folder.root) 1 * downloader.downloadAndUnpackStubJar(_) >> new AbstractMap.SimpleEntry(new StubConfiguration('c:d'), folder.root) when: diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerNotMatchingConsumerSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerNotMatchingConsumerSpec.groovy new file mode 100644 index 0000000000..948cb2881d --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerNotMatchingConsumerSpec.groovy @@ -0,0 +1,73 @@ +/* + * Copyright 2013-2020 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 + * + * https://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 java.util.function.Function + +import org.junit.jupiter.api.Test + +import org.springframework.boot.autoconfigure.AutoConfigurations +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.autoconfigure.ImportAutoConfiguration +import org.springframework.boot.test.context.runner.ApplicationContextRunner +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner +import org.springframework.cloud.contract.stubrunner.spring.StubRunnerConfiguration +import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration +import org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import org.springframework.test.context.ActiveProfiles + +import static org.assertj.core.api.Assertions.assertThat + +@AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers", + repositoryRoot = "classpath:m2repo/repository/", + stubsMode = StubRunnerProperties.StubsMode.REMOTE, + stubsPerConsumer = true) +@ActiveProfiles("streamconsumer") +class StubRunnerStubsPerConsumerNotMatchingConsumerSpec { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(Config)) + .withPropertyValues("spring.application.name=bar-not-matching-consumer", + "stubrunner.jms.enabled=false", "spring.profiles.active=streamconsumer", "spring.cloud.discovery.enabled=false", "spring.cloud.service-registry.auto-registration.enabled=false", "stubrunner.ids=org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers", "stubrunner.repositoryRoot=classpath:m2repo/repository/", "stubrunner.stubsMode=REMOTE", "stubrunner.stubsPerConsumer=true"); + + @Test + void 'should fail to start when JAR is found but there are no stubs or contracts'() { + expect: + this.contextRunner.run((context) -> { + assertThat(context).hasFailed(); + assertThat(context.getStartupFailure()).hasMessageContaining("No stubs or contracts were found for [org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers:0.0.1-SNAPSHOT:stubs] and the switch to fail on no stubs was set") + }); + } + + @Configuration + @EnableAutoConfiguration(exclude = [ZookeeperAutoConfiguration]) + @ImportAutoConfiguration(TestChannelBinderConfiguration.class) + @Import(value = StubRunnerConfiguration) + static class Config { + @Bean + Function output() { + return { Object o -> + println(o) + return o + } + } + } +} diff --git a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/StubRepositoryTest.java b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/StubRepositoryTest.java index 09f4c66b4a..3c57b0af21 100644 --- a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/StubRepositoryTest.java +++ b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/StubRepositoryTest.java @@ -36,7 +36,7 @@ public class StubRepositoryTest { public void should_prefer_custom_yaml_converter_over_standard() { // given: StubRepository repository = new StubRepository(YAML_REPOSITORY_LOCATION, new ArrayList<>(), - new StubRunnerOptionsBuilder().build()); + new StubRunnerOptionsBuilder().build(), null); int expectedDescriptorsSize = 1; // when: