diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/BatchStubRunner.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/BatchStubRunner.java index d2a5e2be07..62bf60bb15 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/BatchStubRunner.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/BatchStubRunner.java @@ -52,24 +52,21 @@ public class BatchStubRunner implements StubRunning { @Override public URL findStubUrl(String groupId, String artifactId) { for (StubRunner stubRunner : this.stubRunners) { - URL url = stubRunner.findStubUrl(groupId, artifactId); - if (url != null) { - return url; - } + try { + return stubRunner.findStubUrl(groupId, artifactId); + } catch (StubNotFoundException e) {} } - return null; + throw new StubNotFoundException(groupId, artifactId); } @Override public URL findStubUrl(String ivyNotation) { - String[] splitString = ivyNotation.split(":"); - if (splitString.length > 3) { - throw new IllegalArgumentException(ivyNotation + " is invalid"); + for (StubRunner stubRunner : this.stubRunners) { + try { + return stubRunner.findStubUrl(ivyNotation); + } catch (StubNotFoundException e) {} } - else if (splitString.length == 2) { - return findStubUrl(splitString[0], splitString[1]); - } - return findStubUrl(null, splitString[0]); + throw new StubNotFoundException(ivyNotation); } @Override diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubConfiguration.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubConfiguration.java index 23a6caccfd..d6b68643fb 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubConfiguration.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubConfiguration.java @@ -20,7 +20,7 @@ import org.springframework.util.StringUtils; /** * Represents a configuration of a single stub. The stub can be described by - * groupId:artifactId:classifier notation + * groupId:artifactId:version:classifier notation */ public class StubConfiguration { private static final String STUB_COLON_DELIMITER = ":"; diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubFinder.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubFinder.java index 1fb77f8403..8d7a473b91 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubFinder.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubFinder.java @@ -28,18 +28,18 @@ public interface StubFinder extends StubTrigger { * URL of the running stub. * * @param groupId - might be null. In that case a search only via artifactId takes place - * @return URL of a running stub or null if not found + * @return URL of a running stub or throws exception if not found */ - URL findStubUrl(String groupId, String artifactId); + URL findStubUrl(String groupId, String artifactId) throws StubNotFoundException; /** - * For the given Ivy notation {@code groupId:artifactId} tries to find the matching - * URL of the running stub. You can also pass only {@code artifactId}. + * For the given Ivy notation {@code [groupId]:artifactId:[version]:[classifier]} tries to + * find the matching URL of the running stub. You can also pass only {@code artifactId}. * * @param ivyNotation - Ivy representation of the Maven artifact - * @return URL of a running stub or null if not found + * @return URL of a running stub or throws exception if not found */ - URL findStubUrl(String ivyNotation); + URL findStubUrl(String ivyNotation) throws StubNotFoundException; /** * Returns all running stubs diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubNotFoundException.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubNotFoundException.java new file mode 100644 index 0000000000..4835d2be0e --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubNotFoundException.java @@ -0,0 +1,18 @@ +package org.springframework.cloud.contract.stubrunner; + +/** + * Exception thrown when a stub was not found + * + * @author Marcin Grzejszczak + * @since 1.0.1 + */ +public class StubNotFoundException extends RuntimeException { + + public StubNotFoundException(String groupId, String artifactId) { + super("Stub not found for groupid [" + groupId + "] and artifactid [" + artifactId + "]"); + } + + public StubNotFoundException(String ivyNotation) { + super("Stub not found for stub with notation [" + ivyNotation + "]"); + } +} 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 c3ed9fa5fd..c2c2f7cf0c 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 @@ -75,12 +75,7 @@ public class StubRunner implements StubRunning { @Override public URL findStubUrl(String ivyNotation) { - String[] splitString = ivyNotation.split(":"); - if (splitString.length == 1) { - // assuming that ivy notation represents artifactId only - return findStubUrl(null, splitString[0]); - } - return findStubUrl(splitString[0], splitString[1]); + return this.localStubRunner.findStubUrl(ivyNotation); } @Override diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java index d66c1e87b1..2f36d99428 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java @@ -85,22 +85,48 @@ class StubRunnerExecutor implements StubFinder { @Override public URL findStubUrl(String groupId, String artifactId) { + URL url = null; if (groupId == null) { - return returnStubUrlIfMatches( - artifactId.equals(this.stubServer.stubConfiguration.artifactId)); + url = findStubUrl( + this.stubServer.stubConfiguration.artifactId.equals(artifactId)); } - return returnStubUrlIfMatches( - artifactId.equals(this.stubServer.stubConfiguration.artifactId) - && groupId.equals(this.stubServer.stubConfiguration.groupId)); + if (url == null) { + url = findStubUrl(this.stubServer.stubConfiguration.artifactId.equals(artifactId) + && this.stubServer.stubConfiguration.groupId.equals(groupId)); + } + if (url == null) { + throw new StubNotFoundException(groupId, artifactId); + } + return url; } @Override public URL findStubUrl(String ivyNotation) { String[] splitString = ivyNotation.split(":"); - if (splitString.length == 1) { - throw new IllegalArgumentException("$ivyNotation is invalid"); + if (splitString.length > 4) { + throw new IllegalArgumentException("[" + ivyNotation + "] is an invalid notation. Pass [groupId]:artifactId[:version][:classifier]."); + } else if (splitString.length == 1) { + return findStubUrl(null, splitString[0]); + } else if (splitString.length == 2) { + return findStubUrl(splitString[0], splitString[1]); + } else if (splitString.length == 3) { + return findStubUrl(groupIdArtifactVersionMatches(splitString)); } - return findStubUrl(splitString[0], splitString[1]); + return findStubUrl(groupIdArtifactVersionMatches(splitString) && classifierMatches(splitString)); + } + + private boolean classifierMatches(String[] splitString) { + return this.stubServer.stubConfiguration.classifier.equals(splitString[3]); + } + + private boolean groupIdArtifactVersionMatches(String[] splitString) { + return this.stubServer.stubConfiguration.artifactId.equals(splitString[0]) + && this.stubServer.stubConfiguration.groupId.equals(splitString[1]) + && this.stubServer.stubConfiguration.version.equals(splitString[2]); + } + + private URL findStubUrl(boolean condition) { + return returnStubUrlIfMatches(condition); } @Override 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 d414ad2b19..85ee3b05d2 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 @@ -60,7 +60,8 @@ public @interface AutoConfigureStubRunner { String repositoryRoot() default ""; /** - * The ids of the stubs to run in "ivy" notation (groupId:artifactId[:classifier]:version[:port]). + * The ids of the stubs to run in "ivy" notation ([groupId]:artifactId:[classifier]:[version][:port]). + * {@code groupId}, {@code classifier}, {@code version} and {@code port} can be optional. */ String[] ids() default {}; 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 be9feded2f..7f195bb5d1 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 @@ -50,7 +50,8 @@ public class StubRunnerProperties { private Resource repositoryRoot; /** - * The ids of the stubs to run in "ivy" notation (groupId:artifactId[:classifier]:version[:port]). + * The ids of the stubs to run in "ivy" notation ([groupId]:artifactId:[classifier]:[version][:port]). + * {@code groupId}, {@code classifier}, {@code version} and {@code port} can be optional. */ private String[] ids = new String[0]; diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/util/StubsParser.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/util/StubsParser.java index f0baf4f416..8638a0b702 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/util/StubsParser.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/util/StubsParser.java @@ -34,7 +34,9 @@ public class StubsParser { * that contains a list of Strings in the format * * * diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/BatchStubRunnerSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/BatchStubRunnerSpec.groovy index 83c46a48d5..7071f45aba 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/BatchStubRunnerSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/BatchStubRunnerSpec.groovy @@ -31,11 +31,13 @@ class BatchStubRunnerSpec extends Specification { batchStubRunner.findStubUrl(KNOWN_STUB_PATH) == KNOWN_STUB_URL } - def 'should return empty optional for unknown stub path'() { + def 'should throw an exception for unknown stub path'() { given: BatchStubRunner batchStubRunner = new BatchStubRunner(runners()) - expect: - !batchStubRunner.findStubUrl(UNKNOWN_STUB_PATH) + when: + batchStubRunner.findStubUrl(UNKNOWN_STUB_PATH) + then: + thrown(StubNotFoundException) } def 'should throw exception if trying to execute not available trigger'() { @@ -51,7 +53,8 @@ class BatchStubRunnerSpec extends Specification { Collection runners() { StubRunner runner = Mock(StubRunner) runner.findStubUrl("group", "knownArtifact") >> KNOWN_STUB_URL - runner.findStubUrl("group", "unknownArtifact") >> null + runner.findStubUrl("group:knownArtifact") >> KNOWN_STUB_URL + runner.findStubUrl("group:unknownArtifact") >> { throw new StubNotFoundException(UNKNOWN_STUB_PATH) } runner.labels() >> ['a:b:c' : ['foo']] return [runner] } 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 68a1b7b3ac..2333fae653 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 @@ -62,8 +62,10 @@ class StubRunnerExecutorSpec extends Specification { StubRunnerExecutor executor = new StubRunnerExecutor(portScanner) when: executor.runStubs(stubRunnerOptions, repository, stub) + and: + executor.findStubUrl("unkowngroup", "unknownartifact") then: - !executor.findStubUrl("unkowngroup", "unknownartifact") + thrown(StubNotFoundException) cleanup: executor.shutdown() } diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy index 42c97883d3..2741a12d62 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.context.SpringBootContextLoader import org.springframework.cloud.contract.stubrunner.StubFinder +import org.springframework.cloud.contract.stubrunner.StubNotFoundException import org.springframework.context.annotation.Configuration import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.ActiveProfiles @@ -57,6 +58,8 @@ class StubRunnerConfigurationSpec extends Specification { stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null stubFinder.findStubUrl('loanIssuance') != null stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') + stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance') + stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs') stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null and: stubFinder.findAllRunningStubs().isPresent('loanIssuance') @@ -67,6 +70,17 @@ class StubRunnerConfigurationSpec extends Specification { "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' } + def 'should throw an exception when stub is not found'() { + when: + stubFinder.findStubUrl('nonExistingService') + then: + thrown(StubNotFoundException) + when: + stubFinder.findStubUrl('nonExistingGroupId', 'nonExistingArtifactId') + then: + thrown(StubNotFoundException) + } + @Configuration @EnableAutoConfiguration static class Config {}