Automatic stub registration (#64)

before this change the support for Stub Runner Boot with Service Discovery was pretty useless since Stub Runner stubbed service discovery. That makes a lot of sense in unit / integration tests but not when you want to do some version of end to end tests. 

With this change you can pass a property to enable automated registration of stubs in Service Discovery.

Technical changes:
* Added Zookeeper registration
* Added Eureka registration
* Added Consul registration
* Added checkstyle plugin - fixed part of exceptions
This commit is contained in:
Marcin Grzejszczak
2016-08-26 16:09:16 +02:00
committed by GitHub
parent ab535dc8ea
commit 64d6d3ef6c
84 changed files with 2452 additions and 326 deletions

51
pom.xml
View File

@@ -27,6 +27,8 @@
<camel.version>2.17.0</camel.version>
<spring-cloud-stream.version>1.0.2.RELEASE</spring-cloud-stream.version>
<spring-boot.version>1.4.0.RELEASE</spring-boot.version>
<checkstyle.version>2.17</checkstyle.version>
<spring-cloud-build.version>1.2.0.BUILD-SNAPSHOT</spring-cloud-build.version>
</properties>
<modules>
@@ -310,6 +312,55 @@
</plugins>
</build>
</profile>
<profile>
<id>checkstyle</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${checkstyle.version}</version>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build-tools</artifactId>
<version>${spring-cloud-build.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<headerLocation>LICENSE.txt</headerLocation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${checkstyle.version}</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<headerLocation>LICENSE.txt</headerLocation>
</configuration>
</plugin>
</plugins>
</reporting>
</profile>
</profiles>
</project>

View File

@@ -20,6 +20,7 @@
<spring-cloud-stream.version>1.0.2.RELEASE</spring-cloud-stream.version>
<spring-cloud-zookeeper.version>1.0.3.BUILD-SNAPSHOT</spring-cloud-zookeeper.version>
<spring-cloud-netflix.version>1.1.5.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<spring-cloud-consul.version>1.0.3.BUILD-SNAPSHOT</spring-cloud-consul.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -173,6 +174,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-consul-dependencies</artifactId>
<version>${spring-cloud-consul.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>

View File

@@ -2,22 +2,21 @@
* Copyright 2009 Wilfred Springer
* Copyright 2012 Jason Pell
* Copyright 2013 Antonio García-Domínguez
*
* <p>
* 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
*
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
*
* <p>
* The class is bundled together with our code because it has not been
* released to any central repository.
*
*/
package repackaged.nl.flotsam.xeger;
@@ -35,67 +34,68 @@ import dk.brics.automaton.Transition;
*/
public class Xeger {
private final Automaton automaton;
private Random random;
private final Automaton automaton;
private Random random;
/**
* Constructs a new instance, accepting the regular expression and the randomizer.
*
* @param regex The regular expression. (Not <code>null</code>.)
* @param random The object that will randomize the way the String is generated. (Not <code>null</code>.)
* @throws IllegalArgumentException If the regular expression is invalid.
*/
public Xeger(String regex, Random random) {
assert regex != null;
assert random != null;
this.automaton = new RegExp(regex).toAutomaton();
this.random = random;
}
/**
* Constructs a new instance, accepting the regular expression and the randomizer.
*
* @param regex The regular expression. (Not <code>null</code>.)
* @param random The object that will randomize the way the String is generated. (Not <code>null</code>.)
* @throws IllegalArgumentException If the regular expression is invalid.
*/
public Xeger(String regex, Random random) {
assert regex != null;
assert random != null;
this.automaton = new RegExp(regex).toAutomaton();
this.random = random;
}
/**
* As {@link Xeger#Xeger(String, java.util.Random)}, creating a {@link java.util.Random} instance
* implicityly.
*
* @param regex as string
*/
public Xeger(String regex) {
this(regex, new Random());
}
/**
* As {@link Xeger#Xeger(String, java.util.Random)}, creating a {@link java.util.Random} instance
* implicityly.
*
* @param regex as string
*/
public Xeger(String regex) {
this(regex, new Random());
}
/**
* Generates a random String that is guaranteed to match the regular expression passed to the constructor.
* @return generated regexp
*/
public String generate() {
StringBuilder builder = new StringBuilder();
generate(builder, automaton.getInitialState());
return builder.toString();
}
/**
* Generates a random String that is guaranteed to match the regular expression passed to the constructor.
* @return generated regexp
*/
public String generate() {
StringBuilder builder = new StringBuilder();
generate(builder, this.automaton.getInitialState());
return builder.toString();
}
private void generate(StringBuilder builder, State state) {
List<Transition> transitions = state.getSortedTransitions(false);
if (transitions.size() == 0) {
assert state.isAccept();
return;
}
int nroptions = state.isAccept() ? transitions.size() : transitions.size() - 1;
int option = Xeger.getRandomInt(0, nroptions, random);
if (state.isAccept() && option == 0) { // 0 is considered stop
return;
}
// Moving on to next transition
Transition transition = transitions.get(option - (state.isAccept() ? 1 : 0));
appendChoice(builder, transition);
generate(builder, transition.getDest());
}
private void generate(StringBuilder builder, State state) {
List<Transition> transitions = state.getSortedTransitions(false);
if (transitions.size() == 0) {
assert state.isAccept();
return;
}
int nroptions = state.isAccept() ? transitions.size() : transitions.size() - 1;
int option = Xeger.getRandomInt(0, nroptions, this.random);
if (state.isAccept() && option == 0) { // 0 is considered stop
return;
}
// Moving on to next transition
Transition transition = transitions.get(option - (state.isAccept() ? 1 : 0));
appendChoice(builder, transition);
generate(builder, transition.getDest());
}
private void appendChoice(StringBuilder builder, Transition transition) {
char c = (char) Xeger.getRandomInt(transition.getMin(), transition.getMax(), random);
builder.append(c);
}
private void appendChoice(StringBuilder builder, Transition transition) {
char c = (char) Xeger
.getRandomInt(transition.getMin(), transition.getMax(), this.random);
builder.append(c);
}
public Random getRandom() {
return random;
return this.random;
}
public void setRandom(Random random) {
@@ -112,7 +112,7 @@ public class Xeger {
*/
static int getRandomInt(int min, int max, Random random) {
// Use random.nextInt as it guarantees a uniform distribution
int maxForRandom=max-min+1;
int maxForRandom = max - min + 1;
return random.nextInt(maxForRandom) + min;
}
}

View File

@@ -236,4 +236,41 @@ For Messaging
[source,groovy,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/server/StubRunnerBootSpec.groovy[tags=boot_usage]
----
----
==== Stub Runner Boot with Service Discovery
One of the possibilities of using Stub Runner Boot is to use it as a feed of stubs for "smoke-tests". What does it mean?
Let's assume that you don't want to deploy 50 microservice to a test environment in order
to check if your application is working fine. You've already executed a suite of tests during the build process
but you would also like to ensure that the packaging of your application is fine. What you can do
is to deploy your application to an environment, start it and run a couple of tests on it to see if
it's working fine. We can call those tests smoke-tests since their idea is to check only a handful
of testing scenarios.
The problem with this approach is such that if you're doing microservices most likely you're
using a service discovery tool. Stub Runner Boot allows you to solve this issue by starting the
required stubs and register them in a service discovery tool. Let's take a look at an example of
such a setup with Eureka. Let's assume that Eureka was already running.
[source,java,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java[tags=stubrunnereureka]
----
As you can see we want to start a Stub Runner Boot server `@EnableStubRunnerServer`, enable Eureka client `@EnableEurekaClient`
and we want to have the stub runner feature turned on `@AutoConfigureStubRunner`.
Now let's assume that we want to start this application so that the stubs get automatically registered.
We can do it by running the app `java -jar ${SYSTEM_PROPS} stub-runner-boot-eureka-example.jar` where
`${SYSTEM_PROPS}` would contain the following list of properties
[source,bash,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java[tags=stubrunnereureka_args]
----
That way your deployed application can send requests to started WireMock servers via the service
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.

View File

@@ -95,6 +95,11 @@
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
@@ -128,13 +133,23 @@
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>

View File

@@ -85,7 +85,7 @@ public class AetherStubDownloader implements StubDownloader {
}
private boolean remoteReposMissing() {
return remoteRepos == null || remoteRepos.isEmpty();
return this.remoteRepos == null || this.remoteRepos.isEmpty();
}
/**
@@ -121,16 +121,16 @@ public class AetherStubDownloader implements StubDownloader {
if (!StringUtils.hasText(resolvedVersion)) {
log.warn("Stub for group [" + stubsGroup + "] module [" + stubsModule
+ "] and classifier [" + classifier + "] not found in "
+ remoteRepos);
+ this.remoteRepos);
return null;
}
Artifact artifact = new DefaultArtifact(stubsGroup, stubsModule, classifier,
ARTIFACT_EXTENSION, resolvedVersion);
ArtifactRequest request = new ArtifactRequest(artifact, remoteRepos, null);
ArtifactRequest request = new ArtifactRequest(artifact, this.remoteRepos, null);
log.info("Resolving artifact [" + artifact
+ "] using remote repositories " + remoteRepos);
+ "] using remote repositories " + this.remoteRepos);
try {
ArtifactResult result = repositorySystem.resolveArtifact(session, request);
ArtifactResult result = this.repositorySystem.resolveArtifact(this.session, request);
log.info("Resolved artifact [" + artifact + "] to "
+ result.getArtifact().getFile());
File temporaryFile = unpackStubJarToATemporaryFolder(
@@ -142,7 +142,7 @@ public class AetherStubDownloader implements StubDownloader {
log.warn(
"Exception occurred while trying to download a stub for group ["
+ stubsGroup + "] module [" + stubsModule
+ "] and classifier [" + classifier + "] in " + remoteRepos,
+ "] and classifier [" + classifier + "] in " + this.remoteRepos,
e);
return null;
}
@@ -187,7 +187,7 @@ public class AetherStubDownloader implements StubDownloader {
remoteRepos, null);
VersionRangeResult rangeResult;
try {
rangeResult = repositorySystem.resolveVersionRange(session,
rangeResult = this.repositorySystem.resolveVersionRange(this.session,
versionRangeRequest);
if (log.isDebugEnabled()) {
log.debug("Resolved version range is [" + rangeResult + "]");
@@ -210,7 +210,7 @@ public class AetherStubDownloader implements StubDownloader {
VersionRequest versionRequest = new VersionRequest(artifact, remoteRepos, null);
VersionResult versionResult;
try {
versionResult = repositorySystem.resolveVersion(session, versionRequest);
versionResult = this.repositorySystem.resolveVersion(this.session, versionRequest);
}
catch (VersionResolutionException e) {
throw new IllegalStateException("Cannot resolve version", e);

View File

@@ -60,13 +60,23 @@ class StubRunnerExecutor implements StubFinder {
public RunningStubs runStubs(StubRunnerOptions stubRunnerOptions,
StubRepository repository, StubConfiguration stubConfiguration) {
if (this.stubServer != null) {
if (log.isDebugEnabled()) {
log.debug("Returning cached version of stubs [" + stubConfiguration.toColonSeparatedDependencyNotation() + "]");
}
return runningStubs();
}
startStubServers(stubRunnerOptions, stubConfiguration, repository);
RunningStubs runningCollaborators = new RunningStubs(Collections
.singletonMap(stubServer.getStubConfiguration(), stubServer.getPort()));
RunningStubs runningCollaborators = runningStubs();
log.info("All stubs are now running " + runningCollaborators.toString());
return runningCollaborators;
}
private RunningStubs runningStubs() {
return new RunningStubs(Collections
.singletonMap(stubServer.getStubConfiguration(), stubServer.getPort()));
}
public void shutdown() {
if (stubServer != null) {
stubServer.stop();

View File

@@ -61,8 +61,10 @@ public class StubRunnerOptions {
*/
final Map<StubConfiguration, Integer> stubIdsToPortMapping;
public StubRunnerOptions(Integer minPortValue, Integer maxPortValue, String stubRepositoryRoot,
boolean workOffline, String stubsClassifier, Collection<StubConfiguration> dependencies, Map<StubConfiguration, Integer> stubIdsToPortMapping) {
public StubRunnerOptions(Integer minPortValue, Integer maxPortValue,
String stubRepositoryRoot, boolean workOffline, String stubsClassifier,
Collection<StubConfiguration> dependencies,
Map<StubConfiguration, Integer> stubIdsToPortMapping) {
this.minPortValue = minPortValue;
this.maxPortValue = maxPortValue;
this.stubRepositoryRoot = stubRepositoryRoot;

View File

@@ -19,8 +19,10 @@ package org.springframework.cloud.contract.stubrunner;
import java.io.Closeable;
public interface StubRunning extends Closeable, StubFinder {
/**
* Runs the stubs and returns the {@link RunningStubs}
* Runs the stubs and returns the {@link RunningStubs}. If the stubs were
* already started then a cached version will be returned.
*/
RunningStubs runStubs();

View File

@@ -21,6 +21,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.contract.stubrunner.StubRunning;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -30,7 +31,7 @@ import org.springframework.web.bind.annotation.RestController;
* @author Marcin Grzejszczak
*/
@RestController
@RequestMapping("/stubs")
@RequestMapping(value = "/stubs", produces = MediaType.APPLICATION_JSON_VALUE)
public class HttpStubsController {
private final StubRunning stubRunning;

View File

@@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.contract.stubrunner.StubFinder;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -36,7 +37,7 @@ import org.springframework.web.bind.annotation.RestController;
* @author Marcin Grzejszczak
*/
@RestController
@RequestMapping("/triggers")
@RequestMapping(value = "/triggers", produces = MediaType.APPLICATION_JSON_VALUE)
public class TriggerController {
private static final Logger log = LoggerFactory.getLogger(TriggerController.class);

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2015 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 java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* Conditional that checks if the user turned off the stubbed discovery mode
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@ConditionalOnProperty(value = "stubrunner.cloud.stubbed.discovery.enabled", havingValue = "false")
public @interface ConditionalOnStubbedDiscoveryDisabled {
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2015 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 java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* Conditional that checks if the user turned on the stubbed discovery mode.
* The feature is turned on by default.
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@ConditionalOnProperty(value = "stubrunner.cloud.stubbed.discovery.enabled", havingValue = "true", matchIfMissing = true)
public @interface ConditionalOnStubbedDiscoveryEnabled {
}

View File

@@ -20,6 +20,8 @@ import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.contract.stubrunner.StubConfiguration;
import org.springframework.cloud.contract.stubrunner.util.StringUtils;
/**
* Maps Ivy based ids to service Ids. You might want to name the service you're calling
@@ -29,7 +31,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* Just provide in your properties file for example:
*
* stubrunner.idsToServiceIds:
* ivyNotation: someValueInsideYourCode
* fraudDetectionServer: someNameThatShouldMapFraudDetectionServer
*
* @author Marcin Grzejszczak
@@ -59,7 +60,17 @@ public class StubMapperProperties {
}
public String fromIvyNotationToId(String ivyNotation) {
return idsToServiceIds.get(ivyNotation);
StubConfiguration stubConfiguration = new StubConfiguration(ivyNotation);
String id = idsToServiceIds.get(ivyNotation);
if (StringUtils.hasText(id)) {
return id;
}
String groupAndArtifact = idsToServiceIds.get(stubConfiguration.getGroupId() +
":" + stubConfiguration.getArtifactId());
if (StringUtils.hasText(groupAndArtifact)) {
return groupAndArtifact;
}
return idsToServiceIds.get(stubConfiguration.getArtifactId());
}
public String fromServiceIdToIvyNotation(String serviceId) {

View File

@@ -16,16 +16,19 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClient;
import org.springframework.cloud.contract.stubrunner.RunningStubs;
import org.springframework.cloud.contract.stubrunner.StubFinder;
import org.springframework.cloud.contract.stubrunner.util.StringUtils;
@@ -40,6 +43,8 @@ import org.springframework.cloud.contract.stubrunner.util.StringUtils;
*/
class StubRunnerDiscoveryClient implements DiscoveryClient {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final DiscoveryClient delegate;
private final StubFinder stubFinder;
private final StubMapperProperties stubMapperProperties;
@@ -48,6 +53,9 @@ class StubRunnerDiscoveryClient implements DiscoveryClient {
StubMapperProperties stubMapperProperties, String springAppName) {
this.delegate = delegate instanceof StubRunnerDiscoveryClient ?
noOpDiscoveryClient(springAppName) : delegate;
if (log.isDebugEnabled()) {
log.debug("Will delegate calls to discovery service [" + this.delegate + "] if a stub is not found");
}
this.stubFinder = stubFinder;
this.stubMapperProperties = stubMapperProperties;
}
@@ -55,6 +63,9 @@ class StubRunnerDiscoveryClient implements DiscoveryClient {
public StubRunnerDiscoveryClient(StubFinder stubFinder,
StubMapperProperties stubMapperProperties, String springAppName) {
this.delegate = noOpDiscoveryClient(springAppName);
if (log.isDebugEnabled()) {
log.debug("Will delegate calls to discovery service [" + this.delegate + "] if a stub is not found");
}
this.stubFinder = stubFinder;
this.stubMapperProperties = stubMapperProperties;
}
@@ -65,12 +76,26 @@ class StubRunnerDiscoveryClient implements DiscoveryClient {
@Override
public String description() {
return delegate.description();
try {
return this.delegate.description();
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Failed to fetch description from delegate", e);
}
}
return "";
}
@Override
public ServiceInstance getLocalServiceInstance() {
return delegate.getLocalServiceInstance();
try {
return this.delegate.getLocalServiceInstance();
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Failed to get local service instance from delegate", e);
}
}
return null;
}
@Override
@@ -78,14 +103,27 @@ class StubRunnerDiscoveryClient implements DiscoveryClient {
String ivyNotation = stubMapperProperties.fromServiceIdToIvyNotation(serviceId);
String serviceToFind = StringUtils.hasText(ivyNotation) ? ivyNotation : serviceId;
URL stubUrl = stubFinder.findStubUrl(serviceToFind);
log.info("Resolved from ivy [" + ivyNotation + "] service to find [" + serviceToFind + "]. "
+ "Found stub is available under URL [" + stubUrl + "]");
if (stubUrl == null) {
return delegate.getInstances(serviceId);
return getInstancesFromDelegate(serviceId);
}
return Collections.<ServiceInstance>singletonList(
new StubRunnerServiceInstance(serviceId, stubUrl.getHost(), stubUrl.getPort(), toUri(stubUrl))
);
}
private List<ServiceInstance> getInstancesFromDelegate(String serviceId) {
try {
return this.delegate.getInstances(serviceId);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Failed to fetch instances from delegate", e);
}
return new ArrayList<>();
}
}
private URI toUri(URL url) {
try {
return url.toURI();
@@ -96,9 +134,20 @@ class StubRunnerDiscoveryClient implements DiscoveryClient {
@Override
public List<String> getServices() {
List<String> services = delegate.getServices();
RunningStubs runningStubs = stubFinder.findAllRunningStubs();
List<String> services = getServicesFromDelegate();
RunningStubs runningStubs = this.stubFinder.findAllRunningStubs();
services.addAll(runningStubs.getAllServicesNames());
return services;
}
private List<String> getServicesFromDelegate() {
try {
return this.delegate.getServices();
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Failed to fetch services from delegate", e);
}
}
return new ArrayList<>();
}
}

View File

@@ -43,6 +43,7 @@ public class StubRunnerSpringCloudAutoConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@Primary
@ConditionalOnStubbedDiscoveryEnabled
public DiscoveryClient stubRunnerDiscoveryClientWrapper(DiscoveryClient discoveryClient,
StubFinder stubFinder,
StubMapperProperties stubMapperProperties,
@@ -51,7 +52,9 @@ public class StubRunnerSpringCloudAutoConfiguration {
}
@Bean
@Primary
@ConditionalOnMissingBean(DiscoveryClient.class)
@ConditionalOnStubbedDiscoveryEnabled
public DiscoveryClient stubRunnerDiscoveryClient(StubFinder stubFinder,
StubMapperProperties stubMapperProperties,
@Value("${spring.application.name:unknown}") String springAppName) {

View File

@@ -0,0 +1,12 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud;
/**
* Contract for registering stubs in a Service Discovery.
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
public interface StubsRegistrar extends AutoCloseable {
void registerStubs();
}

View File

@@ -0,0 +1,86 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud.consul;
import java.lang.invoke.MethodHandles;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.contract.stubrunner.StubConfiguration;
import org.springframework.cloud.contract.stubrunner.StubRunning;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar;
import org.springframework.util.StringUtils;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.agent.model.NewService;
/**
* Registers all stubs in Zookeeper Service Discovery
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
public class ConsulStubsRegistrar implements StubsRegistrar {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final StubRunning stubRunning;
private final ConsulClient consulClient;
private final StubMapperProperties stubMapperProperties;
private final List<NewService> services = new LinkedList<>();
public ConsulStubsRegistrar(StubRunning stubRunning, ConsulClient consulClient,
StubMapperProperties stubMapperProperties) {
this.stubRunning = stubRunning;
this.consulClient = consulClient;
this.stubMapperProperties = stubMapperProperties;
}
@Override public void registerStubs() {
Map<StubConfiguration, Integer> activeStubs = this.stubRunning.runStubs()
.validNamesAndPorts();
for (Map.Entry<StubConfiguration, Integer> entry : activeStubs.entrySet()) {
NewService newService = newService(entry.getKey(), entry.getValue());
this.services.add(newService);
try {
this.consulClient.agentServiceRegister(newService);
if (log.isDebugEnabled()) {
log.debug("Successfully registered stub [" + entry.getKey().toColonSeparatedDependencyNotation()
+ "] in Service Discovery");
}
}
catch (Exception e) {
log.warn("Exception occurred while trying to register a stub [" + entry.getKey().toColonSeparatedDependencyNotation()
+ "] in Service Discovery", e);
}
}
}
protected NewService newService(StubConfiguration stubConfiguration, Integer port) {
NewService newService = new NewService();
newService.setAddress("localhost");
newService.setId(stubConfiguration.getArtifactId());
newService.setName(name(stubConfiguration));
newService.setPort(port);
return newService;
}
protected String name(StubConfiguration stubConfiguration) {
String resolvedName = this.stubMapperProperties.fromIvyNotationToId(
stubConfiguration.toColonSeparatedDependencyNotation());
if (StringUtils.hasText(resolvedName)) {
return resolvedName;
}
return stubConfiguration.getArtifactId();
}
@Override
public void close() throws Exception {
for (NewService service : this.services) {
this.consulClient.agentServiceDeregister(service.getId());
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2013-2016 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.consul;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.contract.stubrunner.StubRunning;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerConfiguration;
import org.springframework.cloud.contract.stubrunner.spring.cloud.ConditionalOnStubbedDiscoveryDisabled;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ecwid.consul.v1.ConsulClient;
/**
* Autoconfiguration for registering stubs in a Zookeeper Service discovery
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
@Configuration
@AutoConfigureAfter(StubRunnerConfiguration.class)
@ConditionalOnClass(ConsulClient.class)
@ConditionalOnStubbedDiscoveryDisabled
@ConditionalOnProperty(value = "stubrunner.cloud.consul.enabled", matchIfMissing = true)
public class StubRunnerSpringCloudConsulAutoConfiguration {
@Bean(initMethod = "registerStubs")
public StubsRegistrar stubsRegistrar(StubRunning stubRunning, ConsulClient consulClient,
StubMapperProperties stubMapperProperties) {
return new ConsulStubsRegistrar(stubRunning, consulClient, stubMapperProperties);
}
}

View File

@@ -0,0 +1,25 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud.eureka;
import com.netflix.appinfo.InstanceInfo;
public class ApplicationStatus {
private Application application;
private InstanceInfo.InstanceStatus status;
public ApplicationStatus(Application application,
InstanceInfo.InstanceStatus status) {
this.application = application;
this.status = status;
}
public ApplicationStatus() {
}
public Application getApplication() {
return application;
}
public InstanceInfo.InstanceStatus getStatus() {
return status;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2015 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.eureka;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* Conditional that checks if Eureka is enabled
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@ConditionalOnProperty(value = "eureka.client.enabled", havingValue = "true", matchIfMissing = true)
@interface ConditionalOnEurekaEnabled {
}

View File

@@ -0,0 +1,334 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud.eureka;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.cloud.netflix.eureka.InstanceInfoFactory;
import org.springframework.http.HttpStatus;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.shared.resolver.ClosableResolver;
import com.netflix.discovery.shared.resolver.EurekaEndpoint;
import com.netflix.discovery.shared.resolver.aws.ApplicationsResolver;
import com.netflix.discovery.shared.resolver.aws.AwsEndpoint;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
import com.netflix.discovery.shared.transport.EurekaHttpClientFactory;
import com.netflix.discovery.shared.transport.EurekaHttpClients;
import com.netflix.discovery.shared.transport.EurekaHttpResponse;
import com.netflix.discovery.shared.transport.EurekaTransportConfig;
import com.netflix.discovery.shared.transport.TransportClientFactory;
import com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient;
import com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory;
import com.sun.jersey.api.client.filter.ClientFilter;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* Taken from https://github.com/spencergibb/spring-cloud-netflix-eureka-lite
*
* @author Spencer Gibb
*
* @since 1.0.0
*/
public class Eureka {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final InetUtils inetUtils;
final CloudEurekaClient eurekaClient;
final EurekaClientConfigBean clientConfig;
final EurekaTransport transport;
public Eureka(InetUtils inetUtils, CloudEurekaClient eurekaClient) {
this.inetUtils = inetUtils;
this.eurekaClient = eurekaClient;
this.clientConfig = new EurekaClientConfigBean();
this.clientConfig.setRegisterWithEureka(false); // turn off registering with eureka, let apps send heartbeats.
this.transport = createTransport();
}
public Registration register(Application application) {
long start = System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug(String.format("Starting registration of %s", application));
}
InstanceInfo instanceInfo = getInstanceInfo(application);
Registration registration = new Registration(instanceInfo, application);
long duration = (System.currentTimeMillis() - start) ;
if (log.isDebugEnabled()) {
log.debug(String.format("Created registration for %s in %s ms", application, duration));
}
register(registration);
return registration;
}
public InstanceInfo getInstanceInfo(Application application, long lastUpdatedTimestamp, long lastDirtyTimestamp) {
InstanceInfo instanceInfo = getInstanceInfo(application);
instanceInfo = new InstanceInfo.Builder(instanceInfo)
.setLastDirtyTimestamp(lastDirtyTimestamp)
.setLastUpdatedTimestamp(lastUpdatedTimestamp)
.build();
return instanceInfo;
}
public InstanceInfo getInstanceInfo(Application application) {
EurekaInstanceConfigBean instanceConfig = new EurekaInstanceConfigBean(inetUtils);
instanceConfig.setInstanceEnabledOnit(true);
instanceConfig.setAppname(application.getName());
instanceConfig.setVirtualHostName(application.getName());
instanceConfig.setInstanceId(application.getInstance_id());
instanceConfig.setHostname(application.getHostname());
instanceConfig.setNonSecurePort(application.getPort());
return new InstanceInfoFactory().create(instanceConfig);
}
public EurekaTransport createTransport() {
TransportClientFactory transportClientFactory = newTransportClientFactory(clientConfig, Collections.<ClientFilter>emptyList());
EurekaTransportConfig transportConfig = clientConfig.getTransportConfig();
ClosableResolver<AwsEndpoint> bootstrapResolver = EurekaHttpClients.newBootstrapResolver(
clientConfig,
transportConfig,
transportClientFactory,
null,
new ApplicationsResolver.ApplicationsSource() {
@Override
public Applications getApplications(int stalenessThreshold, TimeUnit timeUnit) {
long thresholdInMs = TimeUnit.MILLISECONDS.convert(stalenessThreshold, timeUnit);
long delay = eurekaClient.getLastSuccessfulRegistryFetchTimePeriod();
if (delay > thresholdInMs) {
log.info(String.format("Local registry is too stale for local lookup. Threshold:%s, actual:%s",
thresholdInMs, delay));
return null;
} else {
return eurekaClient.getApplications();
}
}
}
);
EurekaHttpClientFactory httpClientFactory;
try {
httpClientFactory = EurekaHttpClients.registrationClientFactory(
bootstrapResolver,
transportClientFactory,
transportConfig
);
} catch (Exception e) {
log.warn("Experimental transport initialization failure", e);
throw new RuntimeException(e);
}
return new EurekaTransport(httpClientFactory, httpClientFactory.newClient(), transportClientFactory, bootstrapResolver);
}
public static TransportClientFactory newTransportClientFactory(
final EurekaClientConfig clientConfig,
final Collection<ClientFilter> additionalFilters) {
final TransportClientFactory jerseyFactory = JerseyEurekaHttpClientFactory.create(
clientConfig, additionalFilters, null, null);
final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory);
return new TransportClientFactory() {
@Override
public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) {
return metricsFactory.newClient(serviceUrl);
}
@Override
public void shutdown() {
metricsFactory.shutdown();
jerseyFactory.shutdown();
}
};
}
/**
* Renew with the eureka service by making the appropriate REST call
*/
public boolean renew(Registration registration) {
InstanceInfo instanceInfo = registration.getInstanceInfo();
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = this.transport.getEurekaHttpClient().sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
if (log.isDebugEnabled()) {
log.debug(String.format("EurekaLite_%s/%s - Heartbeat status: %s", instanceInfo.getAppName(), instanceInfo.getId(), httpResponse.getStatusCode()));
}
if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND.value()) {
log.info(String.format("EurekaLite_%s/%s - Re-registering apps/%s", instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo.getAppName()));
return register(registration);
}
return httpResponse.getStatusCode() == HttpStatus.OK.value();
} catch (Exception e) {
log.error("EurekaLite_"+instanceInfo.getAppName()+"/"+ instanceInfo.getId() + " - was unable to send heartbeat!", e);
return false;
}
}
/**
* Register with the eureka service by making the appropriate REST call.
*/
protected boolean register(Registration registration) {
InstanceInfo instanceInfo = registration.getInstanceInfo();
log.info(String.format("EurekaLite_%s/%s: registering service...", instanceInfo.getAppName(), instanceInfo.getId()));
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = this.transport.getEurekaHttpClient().register(instanceInfo);
} catch (Exception e) {
log.warn("EurekaLite_"+instanceInfo.getAppName()+"/"+ instanceInfo.getId() + " - registration failed " + e.getMessage(), e);
throw e;
}
if (log.isInfoEnabled()) {
log.info(String.format("EurekaLite_%s/%s - registration status: %s", instanceInfo.getAppName(), instanceInfo.getId(), httpResponse.getStatusCode()));
}
return httpResponse.getStatusCode() == HttpStatus.NO_CONTENT.value();
}
public void shutdown(Registration registration) {
InstanceInfo instanceInfo = registration.getInstanceInfo();
try {
EurekaHttpResponse<Void> httpResponse = this.transport.getEurekaHttpClient().cancel(instanceInfo.getAppName(), instanceInfo.getInstanceId());
log.info(String.format("EurekaLite_%s/%s - deregister status: %s", instanceInfo.getAppName(), instanceInfo.getId(), httpResponse.getStatusCode()));
} catch (Exception e) {
log.error("EurekaLite_"+instanceInfo.getAppName()+"/"+ instanceInfo.getId() + " - de-registration failed " + e.getMessage(), e);
}
this.transport.shutdown();
}
}
/**
* Taken from https://github.com/spencergibb/spring-cloud-netflix-eureka-lite
*
* @author Spencer Gibb
*
* @since 1.0.0
*/
class EurekaTransport {
private final EurekaHttpClientFactory eurekaHttpClientFactory;
private final EurekaHttpClient eurekaHttpClient;
private final TransportClientFactory transportClientFactory;
private final ClosableResolver closableResolver;
public EurekaTransport(EurekaHttpClientFactory eurekaHttpClientFactory,
EurekaHttpClient eurekaHttpClient,
TransportClientFactory transportClientFactory,
ClosableResolver closableResolver) {
this.eurekaHttpClientFactory = eurekaHttpClientFactory;
this.eurekaHttpClient = eurekaHttpClient;
this.transportClientFactory = transportClientFactory;
this.closableResolver = closableResolver;
}
public void shutdown() {
eurekaHttpClientFactory.shutdown();
eurekaHttpClient.shutdown();
transportClientFactory.shutdown();
closableResolver.shutdown();
}
public EurekaHttpClientFactory getEurekaHttpClientFactory() {
return eurekaHttpClientFactory;
}
public EurekaHttpClient getEurekaHttpClient() {
return eurekaHttpClient;
}
public TransportClientFactory getTransportClientFactory() {
return transportClientFactory;
}
public ClosableResolver getClosableResolver() {
return closableResolver;
}
}
/**
* Taken from https://github.com/spencergibb/spring-cloud-netflix-eureka-lite
*
* @author Spencer Gibb
*
* @since 1.0.0
*/
class Application {
private String name;
private String instance_id;
private String hostname;
private int port;
public Application(String name, String instance_id, String hostname, int port) {
this.name = name;
this.instance_id = instance_id;
this.hostname = hostname;
this.port = port;
}
public Application() {
}
@JsonIgnore
public String getRegistrationKey() {
return computeRegistrationKey(this.name, instance_id);
}
static String computeRegistrationKey(String name, String instanceId) {
return name + ":" + instanceId;
}
public String getName() {
return name;
}
public String getInstance_id() {
return instance_id;
}
public String getHostname() {
return hostname;
}
public int getPort() {
return port;
}
}
/**
* Scheduled service that automatically will renew registrations in Eureka
*/
class Renewer implements Runnable {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
final Eureka eureka;
final Registration registration;
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Renewer(int flushInterval, Eureka eureka,
Registration registration) {
this.eureka = eureka;
this.registration = registration;
this.scheduler.scheduleWithFixedDelay(this, 0, flushInterval, SECONDS);
}
@Override
public void run() {
if (log.isTraceEnabled()) {
log.trace("Renewing registration [" + this.registration + "]");
}
this.eureka.renew(this.registration);
}
}

View File

@@ -0,0 +1,75 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud.eureka;
import java.lang.invoke.MethodHandles;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.contract.stubrunner.StubConfiguration;
import org.springframework.cloud.contract.stubrunner.StubRunning;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar;
import org.springframework.util.StringUtils;
/**
* Registers all stubs in Eureka Service Discovery
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
public class EurekaStubsRegistrar implements StubsRegistrar {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final StubRunning stubRunning;
private final Eureka eurekaClient;
private final StubMapperProperties stubMapperProperties;
private final List<Renewer> discoveryList = new LinkedList<>();
public EurekaStubsRegistrar(StubRunning stubRunning, Eureka eureka,
StubMapperProperties stubMapperProperties) {
this.stubRunning = stubRunning;
this.stubMapperProperties = stubMapperProperties;
this.eurekaClient = eureka;
}
@Override public void registerStubs() {
Map<StubConfiguration, Integer> activeStubs = this.stubRunning.runStubs()
.validNamesAndPorts();
for (Map.Entry<StubConfiguration, Integer> entry : activeStubs.entrySet()) {
Application application = new Application(name(entry.getKey()), entry.getKey().getArtifactId(), "localhost", entry.getValue());
try {
Registration register = this.eurekaClient.register(application);
this.discoveryList.add(new Renewer(this.eurekaClient.clientConfig.getInstanceInfoReplicationIntervalSeconds() / 2, this.eurekaClient, register));
if (log.isDebugEnabled()) {
log.debug("Successfully registered stub [" + entry.getKey().toColonSeparatedDependencyNotation()
+ "] in Service Discovery");
}
}
catch (Exception e) {
log.warn("Exception occurred while trying to register a stub [" + entry.getKey().toColonSeparatedDependencyNotation()
+ "] in Service Discovery", e);
}
}
}
private String name(StubConfiguration stubConfiguration) {
String resolvedName = this.stubMapperProperties.fromIvyNotationToId(
stubConfiguration.toColonSeparatedDependencyNotation());
if (StringUtils.hasText(resolvedName)) {
return resolvedName;
}
return stubConfiguration.getArtifactId();
}
@Override
public void close() throws Exception {
for (Renewer renewer : this.discoveryList) {
this.eurekaClient.shutdown(renewer.registration);
renewer.scheduler.shutdown();
}
}
}

View File

@@ -0,0 +1,45 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud.eureka;
import com.netflix.appinfo.InstanceInfo;
/**
* Taken from https://github.com/spencergibb/spring-cloud-netflix-eureka-lite
*
* @author Spencer Gibb
*
* @since 1.0.0
*/
public class Registration {
private final InstanceInfo instanceInfo;
private final ApplicationStatus applicationStatus;
public Registration(InstanceInfo instanceInfo, ApplicationStatus applicationStatus) {
this.instanceInfo = instanceInfo;
this.applicationStatus = applicationStatus;
}
public Registration(InstanceInfo instanceInfo, Application application) {
this(instanceInfo, new ApplicationStatus(application, InstanceInfo.InstanceStatus.UP));
}
public String getRegistrationKey() {
return this.applicationStatus.getApplication().getRegistrationKey();
}
public String getApplicationName() {
return this.applicationStatus.getApplication().getName();
}
public InstanceInfo getInstanceInfo() {
return instanceInfo;
}
public ApplicationStatus getApplicationStatus() {
return applicationStatus;
}
@Override public String toString() {
return "Registration{" + "instanceInfo=" + instanceInfo + ", applicationStatus="
+ applicationStatus + '}';
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2013-2016 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.eureka;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.contract.stubrunner.StubRunning;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerConfiguration;
import org.springframework.cloud.contract.stubrunner.spring.cloud.ConditionalOnStubbedDiscoveryDisabled;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar;
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.discovery.EurekaClientConfig;
/**
* Autoconfiguration for registering stubs in a Eureka Service discovery
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
@Configuration
@AutoConfigureAfter({StubRunnerConfiguration.class, EurekaClientAutoConfiguration.class})
@ConditionalOnClass(CloudEurekaClient.class)
@ConditionalOnStubbedDiscoveryDisabled
@ConditionalOnEurekaEnabled
@ConditionalOnProperty(value = "stubrunner.cloud.eureka.enabled", matchIfMissing = true)
public class StubRunnerSpringCloudEurekaAutoConfiguration {
@Bean(initMethod = "registerStubs")
public StubsRegistrar stubsRegistrar(StubRunning stubRunning, Eureka eureka,
StubMapperProperties stubMapperProperties) {
return new EurekaStubsRegistrar(stubRunning, eureka, stubMapperProperties);
}
@Bean(name = "eurekaRegistrar")
public Eureka eureka(InetUtils inetUtils, ApplicationInfoManager manager,
EurekaClientConfig config, ApplicationContext applicationContext) {
return new Eureka(inetUtils, new CloudEurekaClient(manager, config, applicationContext));
}
}

View File

@@ -16,21 +16,24 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud.ribbon;
import com.netflix.loadbalancer.ServerList;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.contract.stubrunner.spring.cloud.ConditionalOnStubbedDiscoveryEnabled;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;
import com.netflix.loadbalancer.ServerList;
@Configuration
@ConditionalOnClass(ServerList.class)
@ConditionalOnBean(StubMapperProperties.class)
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = StubRunnerRibbonConfiguration.class)
@ConditionalOnStubbedDiscoveryEnabled
@ConditionalOnProperty(value = "stubrunner.cloud.ribbon.enabled", matchIfMissing = true)
public class StubRunnerRibbonAutoConfiguration {

View File

@@ -43,9 +43,8 @@ class StubRunnerRibbonServerList implements ServerList<Server> {
private final ServerList<Server> serverList;
StubRunnerRibbonServerList(final StubFinder stubFinder,
final StubMapperProperties stubMapperProperties,
final IClientConfig clientConfig,
final ServerList<?> delegate) {
final StubMapperProperties stubMapperProperties,
final IClientConfig clientConfig, final ServerList<?> delegate) {
String serviceName = clientConfig.getClientName();
String mappedServiceName = StringUtils
.hasText(stubMapperProperties.fromServiceIdToIvyNotation(serviceName)) ?

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2013-2016 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.zookeeper;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.contract.stubrunner.StubRunning;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerConfiguration;
import org.springframework.cloud.contract.stubrunner.spring.cloud.ConditionalOnStubbedDiscoveryDisabled;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar;
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Autoconfiguration for registering stubs in a Zookeeper Service discovery
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
@Configuration
@AutoConfigureAfter(StubRunnerConfiguration.class)
@ConditionalOnBean({ CuratorFramework.class, ZookeeperDiscoveryProperties.class})
@ConditionalOnClass(org.apache.curator.x.discovery.ServiceInstance.class)
@ConditionalOnStubbedDiscoveryDisabled
@ConditionalOnProperty(value = "stubrunner.cloud.zookeeper.enabled", matchIfMissing = true)
public class StubRunnerSpringCloudZookeeperAutoConfiguration {
@Bean(initMethod = "registerStubs")
public StubsRegistrar stubsRegistrar(StubRunning stubRunning, CuratorFramework curatorFramework,
StubMapperProperties stubMapperProperties, ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) {
return new ZookeeperStubsRegistrar(stubRunning, curatorFramework, stubMapperProperties, zookeeperDiscoveryProperties);
}
}

View File

@@ -0,0 +1,101 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud.zookeeper;
import java.lang.invoke.MethodHandles;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.apache.curator.x.discovery.UriSpec;
import org.springframework.cloud.contract.stubrunner.StubConfiguration;
import org.springframework.cloud.contract.stubrunner.StubRunning;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties;
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar;
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties;
import org.springframework.util.StringUtils;
/**
* Registers all stubs in Zookeeper Service Discovery
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
public class ZookeeperStubsRegistrar implements StubsRegistrar {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final StubRunning stubRunning;
private final CuratorFramework curatorFramework;
private final StubMapperProperties stubMapperProperties;
private final ZookeeperDiscoveryProperties zookeeperDiscoveryProperties;
private final List<ServiceDiscovery> discoveryList = new LinkedList<>();
public ZookeeperStubsRegistrar(StubRunning stubRunning, CuratorFramework curatorFramework,
StubMapperProperties stubMapperProperties,
ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) {
this.stubRunning = stubRunning;
this.curatorFramework = curatorFramework;
this.stubMapperProperties = stubMapperProperties;
this.zookeeperDiscoveryProperties = zookeeperDiscoveryProperties;
}
@Override public void registerStubs() {
Map<StubConfiguration, Integer> activeStubs = this.stubRunning.runStubs()
.validNamesAndPorts();
for (Map.Entry<StubConfiguration, Integer> entry : activeStubs.entrySet()) {
ServiceInstance serviceInstance = serviceInstance(entry.getKey(), entry.getValue());
ServiceDiscovery serviceDiscovery = serviceDiscovery(serviceInstance);
this.discoveryList.add(serviceDiscovery);
try {
serviceDiscovery.start();
if (log.isDebugEnabled()) {
log.debug("Successfully registered stub [" + entry.getKey().toColonSeparatedDependencyNotation()
+ "] in Service Discovery");
}
}
catch (Exception e) {
log.warn("Exception occurred while trying to register a stub [" + entry.getKey().toColonSeparatedDependencyNotation()
+ "] in Service Discovery", e);
}
}
}
protected ServiceInstance serviceInstance(StubConfiguration stubConfiguration, int port) {
try {
return ServiceInstance.builder().uriSpec(new UriSpec(this.zookeeperDiscoveryProperties.getUriSpec()))
.address("localhost").port(port).name(name(stubConfiguration))
.build();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
private String name(StubConfiguration stubConfiguration) {
String resolvedName = this.stubMapperProperties.fromIvyNotationToId(
stubConfiguration.toColonSeparatedDependencyNotation());
if (StringUtils.hasText(resolvedName)) {
return resolvedName;
}
return stubConfiguration.getArtifactId();
}
protected ServiceDiscovery serviceDiscovery(ServiceInstance serviceInstance) {
return ServiceDiscoveryBuilder.builder(Void.class)
.basePath(this.zookeeperDiscoveryProperties.getRoot())
.client(this.curatorFramework).thisInstance(serviceInstance).build();
}
@Override
public void close() throws Exception {
for (ServiceDiscovery discovery : this.discoveryList) {
discovery.close();
}
}
}

View File

@@ -5,4 +5,7 @@ org.springframework.cloud.contract.stubrunner.spring.cloud.StubRunnerSpringCloud
org.springframework.cloud.contract.stubrunner.spring.cloud.ribbon.StubRunnerRibbonAutoConfiguration,\
org.springframework.cloud.contract.stubrunner.messaging.stream.StubRunnerStreamConfiguration,\
org.springframework.cloud.contract.stubrunner.messaging.integration.StubRunnerIntegrationConfiguration,\
org.springframework.cloud.contract.stubrunner.messaging.camel.StubRunnerCamelConfiguration
org.springframework.cloud.contract.stubrunner.messaging.camel.StubRunnerCamelConfiguration,\
org.springframework.cloud.contract.stubrunner.spring.cloud.zookeeper.StubRunnerSpringCloudZookeeperAutoConfiguration,\
org.springframework.cloud.contract.stubrunner.spring.cloud.eureka.StubRunnerSpringCloudEurekaAutoConfiguration,\
org.springframework.cloud.contract.stubrunner.spring.cloud.consul.StubRunnerSpringCloudConsulAutoConfiguration

View File

@@ -1,39 +0,0 @@
/*
* Copyright 2012-2013 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;
import org.junit.Ignore
import org.junit.runner.RunWith
import org.junit.runners.Suite
import org.junit.runners.Suite.SuiteClasses
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerConfigurationSpec
import org.springframework.cloud.contract.stubrunner.spring.cloud.StubRunnerSpringCloudAutoConfigurationSpec
/**
* A test suite for probing weird ordering problems in the tests.
*
* @author Dave Syer
*/
@RunWith(Suite)
@SuiteClasses([
StubRunnerSpringCloudAutoConfigurationSpec,
StubRunnerConfigurationSpec
])
@Ignore
public class AdhocTestSuite {
}

View File

@@ -34,4 +34,13 @@ class StubConfigurationSpec extends Specification {
stubConfiguration.classifier == 'classifier'
stubConfiguration.version == 'version'
}
def 'should return ivy notation'() {
given:
String ivy = 'group:artifact:version:classifier'
when:
StubConfiguration stubConfiguration = new StubConfiguration(ivy)
then:
stubConfiguration.toColonSeparatedDependencyNotation() == ivy
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2013-2016 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.serverexamples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
/**
* @author Marcin Grzejszczak
*/
@SpringBootApplication
@EnableStubRunnerServer
@EnableDiscoveryClient
@AutoConfigureStubRunner
public class StubRunnerBootConsulExample {
public static void main(String[] args) {
SpringApplication.run(StubRunnerBootConsulExample.class, args);
}
}
/*
-Dstubrunner.repositoryRoot=classpath:m2repo/repository/
-Dstubrunner.cloud.stubbed.discovery.enabled=false
-Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework.cloud.contract.verifier.stubs:bootService
-Dstubrunner.idsToServiceIds.fraudDetectionServer=someNameThatShouldMapFraudDetectionServer
-Dstubrunner.cloud.consul.enabled=true
-Dstubrunner.camel.enabled=false
-Dspring.cloud.zookeeper.enabled=false
-Deureka.client.enabled=false
-Dspring.cloud.zookeeper.discovery.enabled=false
-Ddebug=true
-Dspring.cloud.consul.host=192.168.99.100
*/

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2013-2016 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.serverexamples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author Marcin Grzejszczak
*/
// tag::stubrunnereureka[]
@SpringBootApplication
@EnableStubRunnerServer
@EnableEurekaClient
@AutoConfigureStubRunner
public class StubRunnerBootEurekaExample {
public static void main(String[] args) {
SpringApplication.run(StubRunnerBootEurekaExample.class, args);
}
}
// end::stubrunnereureka[]
/*
// tag::stubrunnereureka_args[]
-Dstubrunner.repositoryRoot=http://repo.spring.io/snapshots (1)
-Dstubrunner.cloud.stubbed.discovery.enabled=false (2)
-Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework.cloud.contract.verifier.stubs:bootService (3)
-Dstubrunner.idsToServiceIds.fraudDetectionServer=someNameThatShouldMapFraudDetectionServer (4)
(1) - we tell Stub Runner where all the stubs reside
(2) - we don't want the default behaviour where the discovery service is stubbed. That's why the stub registration will be picked
(3) - we provide a list of stubs to download
(4) - we provide a list of artifactId to serviceId mapping
// end::stubrunnereureka_args[]
-Dstubrunner.cloud.eureka.enabled=true
-Dstubrunner.repositoryRoot=classpath:m2repo/repository/
-Dstubrunner.camel.enabled=false
-Dspring.cloud.zookeeper.enabled=false
-Dspring.cloud.zookeeper.discovery.enabled=false
-Ddebug=true
*/

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2013-2016 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.serverexamples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
/**
* @author Marcin Grzejszczak
*/
@SpringBootApplication
@EnableStubRunnerServer
@EnableDiscoveryClient
@AutoConfigureStubRunner
public class StubRunnerBootZookeeperExample {
public static void main(String[] args) {
SpringApplication.run(StubRunnerBootZookeeperExample.class, args);
}
}
/*
-Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework.cloud.contract.verifier.stubs:bootService
-Dstubrunner.idsToServiceIds.fraudDetectionServer=someNameThatShouldMapFraudDetectionServer
-Dstubrunner.cloud.stubbed.discovery.enabled=false
-Dstubrunner.cloud.zookeepr.enabled=true
-Dstubrunner.repositoryRoot=classpath:m2repo/repository/
-Dstubrunner.camel.enabled=false
-Deureka.client.enabled=false
-Ddebug=true
*/

View File

@@ -0,0 +1,21 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
class StubMapperPropertiesSpec extends Specification {
def "should convert ivy notation to serviceId by fallbacking to artifactId if nothing else matches"() {
given:
Map<String, String> idsToServiceIds = [
fraudDetectionServer: 'someNameThatShouldMapFraudDetectionServer'
]
StubMapperProperties properties = new StubMapperProperties(idsToServiceIds: idsToServiceIds)
expect:
'someNameThatShouldMapFraudDetectionServer' == properties.fromIvyNotationToId('fraudDetectionServer')
'someNameThatShouldMapFraudDetectionServer' == properties.fromIvyNotationToId('groupid:fraudDetectionServer')
'someNameThatShouldMapFraudDetectionServer' == properties.fromIvyNotationToId('groupid:fraudDetectionServer:+:classifier')
}
}

View File

@@ -16,34 +16,37 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud
import org.apache.curator.test.TestingServer
import org.junit.AfterClass
import org.junit.BeforeClass
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.cloud.client.discovery.EnableDiscoveryClient
import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.cloud.consul.ConsulAutoConfiguration
import org.springframework.cloud.contract.stubrunner.StubFinder
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.springframework.cloud.zookeeper.ZookeeperProperties
import org.springframework.cloud.zookeeper.discovery.ZookeeperServiceDiscovery
import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
import org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration
import org.springframework.cloud.zookeeper.discovery.RibbonZookeeperAutoConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.ContextConfiguration
import org.springframework.util.SocketUtils
import org.springframework.web.client.RestTemplate
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = ["stubrunner.camel.enabled=false"])
properties = ["stubrunner.camel.enabled=false",
"spring.cloud.zookeeper.enabled=false",
"spring.cloud.consul.enabled=false",
"eureka.client.enabled=false",
"stubrunner.cloud.stubbed.discovery.enabled=true",
"spring.cloud.consul.discovery.enabled=false",
"spring.cloud.zookeeper.discovery.enabled=false"])
// tag::autoconfigure[]
@AutoConfigureStubRunner(
ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
@@ -56,9 +59,6 @@ class StubRunnerSpringCloudAutoConfigurationSpec extends Specification {
@Autowired StubFinder stubFinder
@Autowired @LoadBalanced RestTemplate restTemplate
// TODO: this shouldn't be needed?
@Autowired ZookeeperServiceDiscovery zookeeperServiceDiscovery
@Autowired StubRunnerProperties stubRunnerProperties
@BeforeClass
@AfterClass
@@ -67,10 +67,6 @@ class StubRunnerSpringCloudAutoConfigurationSpec extends Specification {
System.clearProperty("stubrunner.classifier")
}
def setup() {
println "StubRunner properties are [$stubRunnerProperties]"
}
// tag::test[]
def 'should make service discovery work'() {
expect: 'WireMocks are running'
@@ -82,29 +78,11 @@ class StubRunnerSpringCloudAutoConfigurationSpec extends Specification {
}
// end::test[]
TestingServer startTestingServer() {
return new TestingServer(SocketUtils.findAvailableTcpPort())
}
def cleanup() {
zookeeperServiceDiscovery?.serviceDiscovery?.close()
}
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
@EnableAutoConfiguration(exclude = [RibbonZookeeperAutoConfiguration, EurekaClientAutoConfiguration,
ConsulAutoConfiguration, ZookeeperAutoConfiguration])
static class Config {
@Bean
TestingServer testingServer() {
return new TestingServer(SocketUtils.findAvailableTcpPort())
}
@Bean
ZookeeperProperties zookeeperProperties() {
return new ZookeeperProperties(connectString: testingServer().connectString)
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2013-2016 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.consul
import com.ecwid.consul.v1.ConsulClient
import com.ecwid.consul.v1.agent.model.NewService
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher
import org.junit.AfterClass
import org.junit.BeforeClass
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.cloud.client.discovery.EnableDiscoveryClient
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import static org.mockito.BDDMockito.then
import static org.mockito.Matchers.argThat
import static org.mockito.Mockito.mock
/**
* @author Marcin Grzejszczak
*/
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = ["stubrunner.camel.enabled=false",
"eureka.client.enabled=false",
"spring.cloud.zookeeper.enabled=false",
"stubrunner.cloud.stubbed.discovery.enabled=false",
"stubrunner.cloud.eureka.enabled=false",
"spring.cloud.zookeeper.discovery.enabled=false",
"stubrunner.cloud.consul.enabled=true",
"stubrunner.cloud.zookeeper.enabled=false",
"debug=true"])
@AutoConfigureStubRunner( ids =
["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
"org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer",
"org.springframework.cloud.contract.verifier.stubs:bootService"],
repositoryRoot = "classpath:m2repo/repository/")
@DirtiesContext
class StubRunnerSpringCloudConsulAutoConfigurationSpec extends Specification {
@Autowired ConsulClient client
@BeforeClass
@AfterClass
static void setupProps() {
System.clearProperty("stubrunner.stubs.repository.root");
System.clearProperty("stubrunner.stubs.classifier");
}
def 'should make service discovery work for #serviceName'() {
given:
final String expectedId = serviceName.split(':')[0]
final String expectedName = serviceName.split(':')[1]
when: 'Consul registration took place for 3 stubs'
then(client).should().agentServiceRegister(argThat(new NewServiceMatcher(expectedId, expectedName)))
then:
noExceptionThrown()
where:
serviceName << ['loanIssuance:loanIssuance', 'bootService:bootService', 'fraudDetectionServer:someNameThatShouldMapFraudDetectionServer']
}
private static class NewServiceMatcher extends TypeSafeMatcher<NewService> {
private final String expectedId
private final String expectedName
NewServiceMatcher(String expectedId, String expectedName) {
this.expectedId = expectedId
this.expectedName = expectedName
}
@Override
protected boolean matchesSafely(NewService item) {
return item.id == expectedId && item.name == expectedName
}
@Override
void describeTo(Description description) {
}
}
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
static class Config {
@Bean
ConsulClient mockedConsulClient() {
return mock(ConsulClient)
}
}
}

View File

@@ -0,0 +1,9 @@
eureka:
enableSelfPreservation: false
client:
fetchRegistry: false
initialInstanceInfoReplicationIntervalSeconds: 1
instance:
registryFetchIntervalSeconds: 5
leaseRenewalIntervalInSeconds: 5
leaseExpirationDurationInSeconds: 5

View File

@@ -1,4 +1,9 @@
spring.cloud:
zookeeper.enabled: false
consul.enabled: false
eureka.client.enabled: false
stubrunner:
camel.enabled: false
idsToServiceIds:
ivyNotation: someValueInsideYourCode
fraudDetectionServer: someNameThatShouldMapFraudDetectionServer

View File

@@ -40,7 +40,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
final LinkedList<String> methodsBuffer;
DelegatingJsonVerifiable(JsonVerifiable delegate,
LinkedList<String> methodsBuffer) {
LinkedList<String> methodsBuffer) {
this.delegate = delegate;
this.methodsBuffer = new LinkedList<>(methodsBuffer);
}
@@ -62,7 +62,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
}
private void appendMethodWithValue(String methodName, Object value) {
methodsBuffer.offer("." + methodName + "(" + value + ")");
this.methodsBuffer.offer("." + methodName + "(" + value + ")");
}
private void appendMethodWithQuotedValue(String methodName, Object value) {
@@ -71,7 +71,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public MethodBufferingJsonVerifiable contains(Object value) {
DelegatingJsonVerifiable verifiable = new FinishedDelegatingJsonVerifiable(delegate.contains(value), methodsBuffer);
DelegatingJsonVerifiable verifiable = new FinishedDelegatingJsonVerifiable(this.delegate.contains(value), this.methodsBuffer);
verifiable.appendMethodWithQuotedValue("contains", value);
if (isAssertingAValueInArray()) {
verifiable.methodsBuffer.offer(".value()");
@@ -82,8 +82,8 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public MethodBufferingJsonVerifiable field(Object value) {
Object valueToPut = value instanceof ShouldTraverse ? ((ShouldTraverse) value).value : value;
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(delegate.field(valueToPut), methodsBuffer);
if (delegate.isIteratingOverArray() && !(value instanceof ShouldTraverse)) {
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(this.delegate.field(valueToPut), this.methodsBuffer);
if (this.delegate.isIteratingOverArray() && !(value instanceof ShouldTraverse)) {
verifiable.appendMethodWithQuotedValue("contains", valueToPut);
} else {
verifiable.appendMethodWithQuotedValue("field", valueToPut);
@@ -102,51 +102,51 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public MethodBufferingJsonVerifiable array(Object value) {
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(delegate.array(value), methodsBuffer);
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(this.delegate.array(value), this.methodsBuffer);
verifiable.appendMethodWithQuotedValue("array", value);
return verifiable;
}
@Override
public MethodBufferingJsonVerifiable arrayField(Object value) {
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(delegate.field(value).arrayField(), methodsBuffer);
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(this.delegate.field(value).arrayField(), this.methodsBuffer);
verifiable.appendMethodWithQuotedValue("array", value);
return verifiable;
}
@Override
public MethodBufferingJsonVerifiable arrayField() {
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(delegate.arrayField(), methodsBuffer);
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(this.delegate.arrayField(), this.methodsBuffer);
verifiable.methodsBuffer.offer(".arrayField()");
return verifiable;
}
@Override
public MethodBufferingJsonVerifiable array() {
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(delegate.array(), methodsBuffer);
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(this.delegate.array(), this.methodsBuffer);
verifiable.methodsBuffer.offer(".array()");
return verifiable;
}
@Override
public JsonVerifiable elementWithIndex(int i) {
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(delegate.elementWithIndex(i), methodsBuffer);
verifiable.methodsBuffer.offer(".elementWithIndex(" + i + ")");
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(this.delegate.elementWithIndex(i), this.methodsBuffer);
this.methodsBuffer.offer(".elementWithIndex(" + i + ")");
return verifiable;
}
@Override
public MethodBufferingJsonVerifiable iterationPassingArray() {
return new DelegatingJsonVerifiable(delegate, methodsBuffer);
return new DelegatingJsonVerifiable(this.delegate, this.methodsBuffer);
}
@Override
public MethodBufferingJsonVerifiable isEqualTo(String value) {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(delegate.isEqualTo(value), methodsBuffer);
if (delegate.isAssertingAValueInArray() && readyToCheck.methodsBuffer.peekLast().equals(".arrayField()")) {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(this.delegate.isEqualTo(value), this.methodsBuffer);
if (this.delegate.isAssertingAValueInArray() && readyToCheck.methodsBuffer.peekLast().equals(".arrayField()")) {
readyToCheck.appendMethodWithQuotedValue("isEqualTo", escapeJava(value));
readyToCheck.methodsBuffer.offer(".value()");
} else if (delegate.isAssertingAValueInArray() && !readyToCheck.methodsBuffer.peekLast().contains("array")) {
} else if (this.delegate.isAssertingAValueInArray() && !readyToCheck.methodsBuffer.peekLast().contains("array")) {
readyToCheck.methodsBuffer.offer(".value()");
} else {
readyToCheck.appendMethodWithQuotedValue("isEqualTo", escapeJava(value));
@@ -164,11 +164,11 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public MethodBufferingJsonVerifiable isEqualTo(Number value) {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(delegate.isEqualTo(value), methodsBuffer);
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(this.delegate.isEqualTo(value), this.methodsBuffer);
// related to #271 - the problem is with asserting arrays of maps vs arrays of primitives
String last = readyToCheck.methodsBuffer.peekLast();
boolean containsAMatcher = containsAnyMatcher(last);
if (delegate.isAssertingAValueInArray() && containsAMatcher) {
if (this.delegate.isAssertingAValueInArray() && containsAMatcher) {
readyToCheck.methodsBuffer.offer(".value()");
} else {
readyToCheck.appendMethodWithValue("isEqualTo", String.valueOf(value));
@@ -182,15 +182,15 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public MethodBufferingJsonVerifiable isNull() {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(delegate.isNull(), methodsBuffer);
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(this.delegate.isNull(), this.methodsBuffer);
readyToCheck.methodsBuffer.offer(".isNull()");
return readyToCheck;
}
@Override
public MethodBufferingJsonVerifiable matches(String value) {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(delegate.matches(value), methodsBuffer);
if (delegate.isAssertingAValueInArray()) {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(this.delegate.matches(value), this.methodsBuffer);
if (this.delegate.isAssertingAValueInArray()) {
readyToCheck.appendMethodWithQuotedValue("matches", escapeJava(value));
readyToCheck.methodsBuffer.offer(".value()");
} else {
@@ -201,8 +201,8 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public MethodBufferingJsonVerifiable isEqualTo(Boolean value) {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(delegate.isEqualTo(value), methodsBuffer);
if (delegate.isAssertingAValueInArray()) {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(this.delegate.isEqualTo(value), this.methodsBuffer);
if (this.delegate.isAssertingAValueInArray()) {
readyToCheck.methodsBuffer.offer(".value()");
} else {
readyToCheck.appendMethodWithValue("isEqualTo", String.valueOf(value));
@@ -212,12 +212,12 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public MethodBufferingJsonVerifiable value() {
return new FinishedDelegatingJsonVerifiable(delegate, methodsBuffer);
return new FinishedDelegatingJsonVerifiable(this.delegate, this.methodsBuffer);
}
@Override
public boolean assertsSize() {
for (String s : methodsBuffer) {
for (String s : this.methodsBuffer) {
if (s.contains(".hasSize(")) {
return true;
}
@@ -227,7 +227,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public boolean assertsConcreteValue() {
for (String s : methodsBuffer) {
for (String s : this.methodsBuffer) {
if (FIELD_PATTERN.matcher(s).matches()|| ARRAY_PATTERN.matcher(s).matches()) {
return true;
}
@@ -237,39 +237,39 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
@Override
public JsonVerifiable withoutThrowingException() {
return delegate.withoutThrowingException();
return this.delegate.withoutThrowingException();
}
@Override
public String jsonPath() {
return delegate.jsonPath();
return this.delegate.jsonPath();
}
@Override
public void matchesJsonPath(String s) {
delegate.matchesJsonPath(s);
this.delegate.matchesJsonPath(s);
}
@Override
public JsonVerifiable hasSize(int size) {
FinishedDelegatingJsonVerifiable verifiable = new FinishedDelegatingJsonVerifiable(delegate.hasSize(size), methodsBuffer);
FinishedDelegatingJsonVerifiable verifiable = new FinishedDelegatingJsonVerifiable(this.delegate.hasSize(size), this.methodsBuffer);
verifiable.methodsBuffer.offer(".hasSize(" + size + ")");
return verifiable;
}
@Override
public boolean isIteratingOverNamelessArray() {
return delegate.isIteratingOverNamelessArray();
return this.delegate.isIteratingOverNamelessArray();
}
@Override
public boolean isIteratingOverArray() {
return delegate.isIteratingOverArray();
return this.delegate.isIteratingOverArray();
}
@Override
public boolean isAssertingAValueInArray() {
return delegate.isAssertingAValueInArray();
return this.delegate.isAssertingAValueInArray();
}
@Override
@@ -278,7 +278,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
}
private String createMethodString() {
LinkedList<String> queue = new LinkedList<>(methodsBuffer);
LinkedList<String> queue = new LinkedList<>(this.methodsBuffer);
StringBuilder stringBuffer = new StringBuilder();
while (!queue.isEmpty()) {
stringBuffer.append(queue.remove());
@@ -295,33 +295,33 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
DelegatingJsonVerifiable that = (DelegatingJsonVerifiable) o;
if (delegate != null ? !delegate.equals(that.delegate) : that.delegate != null)
if (this.delegate != null ? !this.delegate.equals(that.delegate) : that.delegate != null)
return false;
if (delegate == null) {
if (this.delegate == null) {
return false;
}
if (delegate.jsonPath() == null && that.delegate.jsonPath() == null)
if (this.delegate.jsonPath() == null && that.delegate.jsonPath() == null)
return true;
return delegate.jsonPath().equals(that.delegate.jsonPath());
return this.delegate.jsonPath().equals(that.delegate.jsonPath());
}
@Override
public int hashCode() {
int result = delegate != null ? delegate.jsonPath().hashCode() : 0;
int result = this.delegate != null ? this.delegate.jsonPath().hashCode() : 0;
return 31 * result;
}
@Override
public String toString() {
return "DelegatingJsonVerifiable{" +
"delegate=\n" + delegate +
", methodsBuffer=" + methodsBuffer +
"delegate=\n" + this.delegate +
", methodsBuffer=" + this.methodsBuffer +
'}';
}
@Override
public <T> T read(Class<T> aClass) {
return delegate.read(aClass);
return this.delegate.read(aClass);
}
}

View File

@@ -31,7 +31,7 @@ import com.toomuchcoding.jsonassert.JsonVerifiable;
class FinishedDelegatingJsonVerifiable extends DelegatingJsonVerifiable {
FinishedDelegatingJsonVerifiable(JsonVerifiable delegate,
LinkedList<String> methodsBuffer) {
LinkedList<String> methodsBuffer) {
super(delegate, methodsBuffer);
}

View File

@@ -51,8 +51,8 @@ public class CamelStubMessages implements MessageVerifier<Message> {
@Override
public void send(Message message, String destination) {
try {
ProducerTemplate producerTemplate = context.createProducerTemplate();
Exchange exchange = new DefaultExchange(context);
ProducerTemplate producerTemplate = this.context.createProducerTemplate();
Exchange exchange = new DefaultExchange(this.context);
exchange.setIn(message);
producerTemplate.send(destination, exchange);
} catch (Exception e) {
@@ -64,13 +64,13 @@ public class CamelStubMessages implements MessageVerifier<Message> {
@Override
public <T> void send(T payload, Map<String, Object> headers, String destination) {
send(builder.create(payload, headers), destination);
send(this.builder.create(payload, headers), destination);
}
@Override
public Message receive(String destination, long timeout, TimeUnit timeUnit) {
try {
ConsumerTemplate consumerTemplate = context.createConsumerTemplate();
ConsumerTemplate consumerTemplate = this.context.createConsumerTemplate();
Exchange exchange = consumerTemplate.receive(destination, timeUnit.toMillis(timeout));
return exchange.getIn();
} catch (Exception e) {

View File

@@ -49,13 +49,13 @@ public class SpringIntegrationStubMessages implements
@Override
public <T> void send(T payload, Map<String, Object> headers, String destination) {
send(builder.create(payload, headers), destination);
send(this.builder.create(payload, headers), destination);
}
@Override
public void send(Message<?> message, String destination) {
try {
MessageChannel messageChannel = context.getBean(destination, MessageChannel.class);
MessageChannel messageChannel = this.context.getBean(destination, MessageChannel.class);
messageChannel.send(message);
} catch (Exception e) {
log.error("Exception occurred while trying to send a message [" + message + "] " +
@@ -67,7 +67,7 @@ public class SpringIntegrationStubMessages implements
@Override
public Message<?> receive(String destination, long timeout, TimeUnit timeUnit) {
try {
PollableChannel messageChannel = context.getBean(destination, PollableChannel.class);
PollableChannel messageChannel = this.context.getBean(destination, PollableChannel.class);
return messageChannel.receive(timeUnit.toMillis(timeout));
} catch (Exception e) {
log.error("Exception occurred while trying to read a message from " +

View File

@@ -43,7 +43,7 @@ public class ContractVerifierMessage {
}
public Object getPayload() {
return payload;
return this.payload;
}
public void setPayload(Object payload) {
@@ -51,11 +51,11 @@ public class ContractVerifierMessage {
}
public Map<String, Object> getHeaders() {
return headers;
return this.headers;
}
public Object getHeader(String name) {
return headers.get(name);
return this.headers.get(name);
}
public void setHeaders(Map<String, Object> headers) {

View File

@@ -33,11 +33,11 @@ public class ContractVerifierMessaging<M> {
}
public void send(ContractVerifierMessage message, String destination) {
exchange.send(message.getPayload(), message.getHeaders(), destination);
this.exchange.send(message.getPayload(), message.getHeaders(), destination);
}
public ContractVerifierMessage receive(String destination) {
return convert(exchange.receive(destination));
return convert(this.exchange.receive(destination));
}
public <T> ContractVerifierMessage create(T payload, Map<String, Object> headers) {

View File

@@ -41,6 +41,6 @@ public class ContractVerifierObjectMapper {
if (payload instanceof String) {
return payload.toString();
}
return objectMapper.writeValueAsString(payload);
return this.objectMapper.writeValueAsString(payload);
}
}

View File

@@ -49,13 +49,13 @@ public class StreamStubMessages implements MessageVerifier<Message<?>> {
@Override
public <T> void send(T payload, Map<String, Object> headers, String destination) {
send(builder.create(payload, headers), destination);
send(this.builder.create(payload, headers), destination);
}
@Override
public void send(Message<?> message, String destination) {
try {
MessageChannel messageChannel = context
MessageChannel messageChannel = this.context
.getBean(resolvedDestination(destination), MessageChannel.class);
messageChannel.send(message);
}
@@ -69,9 +69,9 @@ public class StreamStubMessages implements MessageVerifier<Message<?>> {
@Override
public Message<?> receive(String destination, long timeout, TimeUnit timeUnit) {
try {
MessageChannel messageChannel = context
MessageChannel messageChannel = this.context
.getBean(resolvedDestination(destination), MessageChannel.class);
return messageCollector.forChannel(messageChannel).poll(timeout, timeUnit);
return this.messageCollector.forChannel(messageChannel).poll(timeout, timeUnit);
}
catch (Exception e) {
log.error("Exception occurred while trying to read a message from "
@@ -81,7 +81,7 @@ public class StreamStubMessages implements MessageVerifier<Message<?>> {
}
private String resolvedDestination(String destination) {
ChannelBindingServiceProperties channelBindingServiceProperties = context
ChannelBindingServiceProperties channelBindingServiceProperties = this.context
.getBean(ChannelBindingServiceProperties.class);
for (Map.Entry<String, BindingProperties> entry : channelBindingServiceProperties
.getBindings().entrySet()) {

View File

@@ -43,72 +43,72 @@ public class ContractVerifierMessagingUtil {
@Override
public int size() {
return delegate.size();
return this.delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
return this.delegate.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
return this.delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
return this.delegate.containsValue(value);
}
@Override
public Object get(Object key) {
return delegate.get(key);
return this.delegate.get(key);
}
@Override
public Object put(String key, Object value) {
return delegate.put(key, value);
return this.delegate.put(key, value);
}
@Override
public Object remove(Object key) {
return delegate.remove(key);
return this.delegate.remove(key);
}
@Override
public void putAll(Map<? extends String, ?> m) {
delegate.putAll(m);
this.delegate.putAll(m);
}
@Override
public void clear() {
delegate.clear();
this.delegate.clear();
}
@Override
public Set<String> keySet() {
return delegate.keySet();
return this.delegate.keySet();
}
@Override
public Collection<Object> values() {
return delegate.values();
return this.delegate.values();
}
@Override
public Set<Entry<String, Object>> entrySet() {
return delegate.entrySet();
return this.delegate.entrySet();
}
@Override
public boolean equals(Object o) {
return delegate.equals(o);
return this.delegate.equals(o);
}
@Override
public int hashCode() {
return delegate.hashCode();
return this.delegate.hashCode();
}
}
}

View File

@@ -25,13 +25,11 @@ import java.lang.annotation.Target;
import org.springframework.boot.test.autoconfigure.properties.PropertyMapping;
import org.springframework.context.annotation.Import;
import com.github.tomakehurst.wiremock.core.Options;
/**
* Annotation for test classes that want to start a WireMock server as part of the Spring
* Application Context. The port, https port and stub locations (if any) can all be
* controlled directly here. For more fine-grained control of the server instance add a
* bean of type {@link Options} to the application context.
* bean of type {@link com.github.tomakehurst.wiremock.core.Options} to the application context.
*
* @author Dave Syer
*

View File

@@ -139,9 +139,9 @@ class SpringBootHttpServer
private ContainerProperties container() {
if (this.context != null) {
return context.getBean(ContainerProperties.class);
return this.context.getBean(ContainerProperties.class);
}
return new ContainerProperties(options);
return new ContainerProperties(this.options);
}
@Override
@@ -253,7 +253,7 @@ class WiremockServerConfiguration {
WiremockServerConfiguration.this.adminRequestHandler);
servletContext.setAttribute(StubRequestHandler.class.getName(),
WiremockServerConfiguration.this.stubRequestHandler);
servletContext.setAttribute(Notifier.KEY, options.notifier());
servletContext.setAttribute(Notifier.KEY, WiremockServerConfiguration.this.options.notifier());
}
};
}
@@ -278,13 +278,13 @@ class ContainerProperties {
}
public int port() {
if (options.httpsSettings().enabled()) {
return options.portNumber();
if (this.options.httpsSettings().enabled()) {
return this.options.portNumber();
}
if (this.localPort != null) {
return this.localPort;
}
EmbeddedWebApplicationContext embedded = (EmbeddedWebApplicationContext) context;
EmbeddedWebApplicationContext embedded = (EmbeddedWebApplicationContext) this.context;
return embedded.getEmbeddedServletContainer().getPort();
}
@@ -330,9 +330,9 @@ class ContainerConfiguration {
@EventListener
public void serverUp(EmbeddedServletContainerInitializedEvent event) {
if (connector != null) {
container.setLocalPort(connector.getLocalPort());
container
if (this.connector != null) {
this.container.setLocalPort(this.connector.getLocalPort());
this.container
.setLocalHttpsPort(event.getEmbeddedServletContainer().getPort());
}
}
@@ -367,8 +367,8 @@ class ContainerConfiguration {
undertow.addBuilderCustomizers(new UndertowBuilderCustomizer() {
@Override
public void customize(Builder builder) {
builder.addHttpListener(options.portNumber(), "localhost");
UndertowContainerConfiguration.this.port = options.portNumber();
builder.addHttpListener(UndertowContainerConfiguration.this.options.portNumber(), "localhost");
UndertowContainerConfiguration.this.port = UndertowContainerConfiguration.this.options.portNumber();
}
});
}
@@ -377,10 +377,10 @@ class ContainerConfiguration {
@EventListener
public void serverUp(EmbeddedServletContainerInitializedEvent event) {
if (port != null) {
if (this.port != null) {
// TODO: make it dynamic as well
container.setLocalPort(port);
container
this.container.setLocalPort(this.port);
this.container
.setLocalHttpsPort(event.getEmbeddedServletContainer().getPort());
}
}
@@ -419,7 +419,7 @@ class ContainerConfiguration {
Server server) {
ServerConnector connector = new ServerConnector(server, -1, -1);
connector.setHost("localhost");
connector.setPort(options.portNumber());
connector.setPort(this.options.portNumber());
for (ConnectionFactory connectionFactory : connector
.getConnectionFactories()) {
if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
@@ -433,9 +433,9 @@ class ContainerConfiguration {
@EventListener
public void serverUp(EmbeddedServletContainerInitializedEvent event) {
if (connector != null) {
container.setLocalPort(connector.getLocalPort());
container
if (this.connector != null) {
this.container.setLocalPort(this.connector.getLocalPort());
this.container
.setLocalHttpsPort(event.getEmbeddedServletContainer().getPort());
}
}

View File

@@ -71,29 +71,29 @@ public class WireMockConfiguration implements SmartLifecycle {
@PostConstruct
public void init() throws IOException {
if (options == null) {
if (this.options == null) {
com.github.tomakehurst.wiremock.core.WireMockConfiguration factory = WireMockSpring
.options();
if (wireMock.getPort() != 8080) {
factory.port(wireMock.getPort());
if (this.wireMock.getPort() != 8080) {
factory.port(this.wireMock.getPort());
}
if (wireMock.getHttpsPort() != -1) {
factory.httpsPort(wireMock.getHttpsPort());
if (this.wireMock.getHttpsPort() != -1) {
factory.httpsPort(this.wireMock.getHttpsPort());
}
this.options = factory;
}
server = new WireMockServer(options);
this.server = new WireMockServer(this.options);
registerStubs();
if (!beanFactory.containsBean("wireMockServer")) {
beanFactory.registerSingleton("wireMockServer", server);
if (!this.beanFactory.containsBean("wireMockServer")) {
this.beanFactory.registerSingleton("wireMockServer", this.server);
}
}
private void registerStubs() throws IOException {
if (StringUtils.hasText(wireMock.getStubs())) {
if (StringUtils.hasText(this.wireMock.getStubs())) {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
resourceLoader);
String pattern = wireMock.getStubs();
this.resourceLoader);
String pattern = this.wireMock.getStubs();
if (!pattern.contains("*")) {
if (!pattern.endsWith("/")) {
pattern = pattern + "/";
@@ -101,7 +101,7 @@ public class WireMockConfiguration implements SmartLifecycle {
pattern = pattern + "**/*.json";
}
for (Resource resource : resolver.getResources(pattern)) {
server.addStubMapping(StubMapping.buildFrom(StreamUtils.copyToString(
this.server.addStubMapping(StubMapping.buildFrom(StreamUtils.copyToString(
resource.getInputStream(), Charset.forName("UTF-8"))));
}
}
@@ -109,22 +109,22 @@ public class WireMockConfiguration implements SmartLifecycle {
@Override
public void start() {
server.start();
WireMock.configureFor("localhost", server.port());
running = true;
this.server.start();
WireMock.configureFor("localhost", this.server.port());
this.running = true;
}
@Override
public void stop() {
if (running) {
server.stop();
running = false;
if (this.running) {
this.server.stop();
this.running = false;
}
}
@Override
public boolean isRunning() {
return running;
return this.running;
}
@Override
@@ -154,7 +154,7 @@ class WireMockProperties {
private String stubs;
public int getPort() {
return port;
return this.port;
}
public void setPort(int port) {
@@ -162,7 +162,7 @@ class WireMockProperties {
}
public int getHttpsPort() {
return httpsPort;
return this.httpsPort;
}
public void setHttpsPort(int httpsPort) {
@@ -170,7 +170,7 @@ class WireMockProperties {
}
public String getStubs() {
return stubs;
return this.stubs;
}
public void setStubs(String stubs) {

View File

@@ -16,9 +16,6 @@
package org.springframework.cloud.contract.wiremock;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -45,6 +42,9 @@ import com.github.tomakehurst.wiremock.matching.MultiValuePattern;
import com.github.tomakehurst.wiremock.matching.RequestPattern;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
/**
* Convenience class for loading WireMock stubs into a {@link MockRestServiceServer}. In
* this way using a {@link RestTemplate} can mock the responses from a server using
@@ -136,7 +136,7 @@ public class WireMockRestServiceServer {
* @return a MockRestServiceServer
*/
public MockRestServiceServer build() {
MockRestServiceServer server = builder.build();
MockRestServiceServer server = this.builder.build();
for (String location : this.locations) {
try {
for (Resource resource : this.resolver.getResources(pattern(location))) {

View File

@@ -24,7 +24,6 @@ import org.apache.http.ssl.SSLContexts;
import org.junit.Assert;
import org.springframework.util.ClassUtils;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
/**
@@ -37,7 +36,7 @@ import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
* WireMockSpring.config());
* </pre>
*
* and then use {@link WireMock} as normal in your test methods.
* and then use {@link com.github.tomakehurst.wiremock.client.WireMock} as normal in your test methods.
*
* @author Dave Syer
*

View File

@@ -67,20 +67,20 @@ public class ContractRequestHandler implements ResultHandler {
Map<String, Object> configuration = getConfiguration(result);
String actual = StreamUtils.copyToString(request.getInputStream(),
Charset.forName("UTF-8"));
for (JsonPath jsonPath : jsonPaths.values()) {
for (JsonPath jsonPath : this.jsonPaths.values()) {
new JsonPathValue(jsonPath, actual).assertHasValue(Object.class, "an object");
}
configuration.put("contract.jsonPaths", jsonPaths.keySet());
if (contentType != null) {
configuration.put("contract.contentType", contentType);
configuration.put("contract.jsonPaths", this.jsonPaths.keySet());
if (this.contentType != null) {
configuration.put("contract.contentType", this.contentType);
String resultType = request.getContentType();
assertThat(resultType).isNotNull().as("no content type");
assertThat(contentType.includes(MediaType.valueOf(resultType))).isTrue()
assertThat(this.contentType.includes(MediaType.valueOf(resultType))).isTrue()
.as("content type did not match");
}
if (builder != null) {
builder.willReturn(getResponseDefinition(result));
StubMapping stubMapping = builder.build();
if (this.builder != null) {
this.builder.willReturn(getResponseDefinition(result));
StubMapping stubMapping = this.builder.build();
MatchResult match = stubMapping.getRequest()
.match(new WireMockHttpServletRequestAdapter(request));
assertThat(match.isExactMatch()).as("wiremock did not match request").isTrue();
@@ -137,7 +137,7 @@ public class ContractRequestHandler implements ResultHandler {
(expression == null ? null : expression),
"expression must not be null or empty");
expression = String.format(expression, args);
jsonPaths.put(expression, JsonPath.compile(expression));
this.jsonPaths.put(expression, JsonPath.compile(expression));
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.cloud.contract.wiremock.restdocs;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
@@ -24,7 +23,7 @@ import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
/**
* Custom configuration for Spring RestDocs that adds a WireMock snippet (for generating
* JSON stubs). Applied automatically if you use
* {@link AutoConfigureRestDocs @AutoConfigureRestDocs} in your test case and this class
* {@link org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs @AutoConfigureRestDocs} in your test case and this class
* is available. JSON stubs are generated and added to the restdocs path under "stubs".
*
* @see WireMockRestDocs for a convenient entry point for customizing and asserting the

View File

@@ -16,16 +16,6 @@
package org.springframework.cloud.contract.wiremock.restdocs;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.delete;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.put;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -50,6 +40,16 @@ import com.github.tomakehurst.wiremock.http.HttpHeaders;
import com.github.tomakehurst.wiremock.matching.UrlPattern;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.delete;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.put;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
public class WireMockSnippet implements Snippet {
private String snippetName = "stubs";
@@ -66,10 +66,10 @@ public class WireMockSnippet implements Snippet {
@Override
public void document(Operation operation) throws IOException {
extractMatchers(operation);
if (stubMapping == null) {
stubMapping = request(operation).willReturn(response(operation)).build();
if (this.stubMapping == null) {
this.stubMapping = request(operation).willReturn(response(operation)).build();
}
String json = Json.write(stubMapping);
String json = Json.write(this.stubMapping);
RestDocumentationContext context = (RestDocumentationContext) operation
.getAttributes().get(RestDocumentationContext.class.getName());
File output = new File(context.getOutputDirectory(),
@@ -81,15 +81,15 @@ public class WireMockSnippet implements Snippet {
}
private void extractMatchers(Operation operation) {
stubMapping = (StubMapping) operation.getAttributes().get("contract.stubMapping");
if (stubMapping != null) {
this.stubMapping = (StubMapping) operation.getAttributes().get("contract.stubMapping");
if (this.stubMapping != null) {
return;
}
@SuppressWarnings("unchecked")
Set<String> jsonPaths = (Set<String>) operation.getAttributes()
.get("contract.jsonPaths");
this.jsonPaths = jsonPaths;
contentType = (MediaType) operation.getAttributes().get("contract.contentType");
this.contentType = (MediaType) operation.getAttributes().get("contract.contentType");
}
private ResponseDefinitionBuilder response(Operation operation) {
@@ -107,7 +107,7 @@ public class WireMockSnippet implements Snippet {
.getHeaders();
// TODO: whitelist headers
for (String name : headers.keySet()) {
if (!headerBlackList.contains(name.toLowerCase())) {
if (!this.headerBlackList.contains(name.toLowerCase())) {
if ("content-type".equalsIgnoreCase(name) && this.contentType != null) {
continue;
}
@@ -138,8 +138,8 @@ public class WireMockSnippet implements Snippet {
private RemoteMappingBuilder<?, ?> bodyPattern(RemoteMappingBuilder<?, ?> builder,
String content) {
if (jsonPaths != null) {
for (String jsonPath : jsonPaths) {
if (this.jsonPaths != null) {
for (String jsonPath : this.jsonPaths) {
builder.withRequestBody(matchingJsonPath(jsonPath));
}
}
@@ -158,7 +158,7 @@ public class WireMockSnippet implements Snippet {
.getHeaders();
HttpHeaders result = new HttpHeaders();
for (String name : headers.keySet()) {
if (!headerBlackList.contains(name.toLowerCase())) {
if (!this.headerBlackList.contains(name.toLowerCase())) {
result = result.plus(new HttpHeader(name, headers.get(name)));
}
}

View File

@@ -22,6 +22,8 @@
<module>samples-messaging-spring</module>
<module>samples-messaging-stream</module>
<module>samples-messaging-integration</module>
<module>spring-cloud-contract-stub-runner-boot-eureka</module>
<module>spring-cloud-contract-stub-runner-boot-zookeeper</module>
<module>spring-cloud-contract-stub-runner-camel</module>
<module>spring-cloud-contract-stub-runner-integration</module>
<module>spring-cloud-contract-stub-runner-stream</module>

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-tests</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-stub-runner-boot-eureka</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Contract Stub Runner Boot Eureka</name>
<description>Spring Cloud Contract Stub Runner Boot Eureka</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-stub-runner</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner-jetty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.solidsoft.spock</groupId>
<artifactId>spock-global-unroll</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2013-2016 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.eureka
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.cloud.contract.stubrunner.StubFinder
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner
import org.springframework.cloud.netflix.eureka.EnableEurekaClient
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.http.client.ClientHttpResponse
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.ContextConfiguration
import org.springframework.web.client.*
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
import spock.util.concurrent.PollingConditions
/**
* @author Marcin Grzejszczak
*/
//TODO: Speed up this test somehow
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = ["stubrunner.cloud.eureka.enabled=true",
"stubrunner.cloud.stubbed.discovery.enabled=false",
"stubrunner.cloud.ribbon.enabled=false",
"eureka.client.enabled=true",
"eureka.instance.leaseRenewalIntervalInSeconds=1",
"ribbon.ServerListRefreshInterval=100"])
@AutoConfigureStubRunner( ids =
["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
"org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer",
"org.springframework.cloud.contract.verifier.stubs:bootService"],
repositoryRoot = "classpath:m2repo/repository/")
@DirtiesContext
class StubRunnerSpringCloudEurekaAutoConfigurationSpec extends Specification {
@Autowired StubFinder stubFinder
@Autowired @LoadBalanced RestTemplate restTemplate
@Shared @AutoCleanup ConfigurableApplicationContext eurekaServer
void setupSpec() {
System.clearProperty("stubrunner.stubs.repository.root")
System.clearProperty("stubrunner.stubs.classifier")
eurekaServer = SpringApplication.run(EurekaServer,
"--stubrunner.cloud.eureka.enabled=true",
"--stubrunner.cloud.stubbed.discovery.enabled=false",
"--eureka.client.enabled=true",
"--server.port=8761",
"--spring.profiles.active=eureka")
}
void cleanupSpec() {
System.clearProperty("stubrunner.stubs.repository.root")
System.clearProperty("stubrunner.stubs.classifier")
}
PollingConditions conditions = new PollingConditions(timeout: 40, delay: 1)
def 'should make service discovery work'() {
expect: 'WireMocks are running'
"${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
"${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
and: 'Stubs can be reached via load service discovery'
conditions.eventually {
assert restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance'
}
restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer'
}
@Configuration
@EnableAutoConfiguration
@EnableEurekaClient
static class Config {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
def template = new RestTemplate() {
@Override
protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor) throws RestClientException {
try {
return super.doExecute(url, method, requestCallback, responseExtractor)
} catch (Exception e) {
throw new AssertionError(e)
}
}
}
template.errorHandler = new DefaultResponseErrorHandler() {
@Override
void handleError(ClientHttpResponse response) throws IOException {
try {
super.handleError(response)
} catch (Exception e) {
throw new AssertionError(e)
}
}
}
return template
}
}
@Configuration
@EnableAutoConfiguration
@EnableEurekaServer
static class EurekaServer {
}
}

View File

@@ -0,0 +1,4 @@
stubrunner:
camel.enabled: false
idsToServiceIds:
fraudDetectionServer: someNameThatShouldMapFraudDetectionServer

View File

@@ -0,0 +1,25 @@
<!--
~ Copyright 2013-2016 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.
-->
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.springframework.cloud" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>bootService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<metadata>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>bootService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>fraudDetectionServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<metadata>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>fraudDetectionServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>loanIssuance</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<metadata>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>loanIssuance</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062111</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-tests</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-stub-runner-boot-zookeeper</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Contract Stub Runner Boot Zookeeper</name>
<description>Spring Cloud Contract Stub Runner Boot Zookeeper</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-stub-runner</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner-jetty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.solidsoft.spock</groupId>
<artifactId>spock-global-unroll</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.spring.cloud
package org.springframework.cloud.contract.stubrunner.spring.cloud.zookeeper
import org.apache.curator.test.TestingServer
import org.junit.AfterClass
import org.junit.BeforeClass
import org.springframework.beans.factory.annotation.Autowired
@@ -26,31 +27,35 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient
import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.cloud.contract.stubrunner.StubFinder
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner
import org.springframework.cloud.zookeeper.discovery.RibbonZookeeperAutoConfiguration
import org.springframework.cloud.zookeeper.ZookeeperProperties
import org.springframework.cloud.zookeeper.discovery.ZookeeperServiceDiscovery
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.ContextConfiguration
import org.springframework.util.SocketUtils
import org.springframework.web.client.RestTemplate
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = ["stubrunner.camel.enabled=false",
"spring.cloud.zookeeper.enabled=false",
"spring.cloud.zookeeper.discovery.enabled=false"])
"stubrunner.cloud.stubbed.discovery.enabled=false",
"debug=true"])
@AutoConfigureStubRunner( ids =
["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
"org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer",
"org.springframework.cloud.contract.verifier.stubs:bootService"],
repositoryRoot = "classpath:m2repo/repository/")
@DirtiesContext
class StubRunnerSpringCloudAutoConfigurationWithoutDiscoverySpec extends Specification {
class StubRunnerSpringCloudZookeeperAutoConfigurationSpec extends Specification {
@Autowired StubFinder stubFinder
@Autowired @LoadBalanced RestTemplate restTemplate
@Autowired ZookeeperServiceDiscovery zookeeperServiceDiscovery
@BeforeClass
@AfterClass
@@ -68,11 +73,31 @@ class StubRunnerSpringCloudAutoConfigurationWithoutDiscoverySpec extends Specifi
restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer'
}
def 'should have all apps registered in Service Discovery'() {
expect:
!zookeeperServiceDiscovery.getServiceDiscovery().queryForInstances('loanIssuance').empty
!zookeeperServiceDiscovery.getServiceDiscovery().queryForInstances('someNameThatShouldMapFraudDetectionServer').empty
}
def cleanup() {
zookeeperServiceDiscovery?.serviceDiscovery?.close()
}
@Configuration
@EnableAutoConfiguration(exclude = [RibbonZookeeperAutoConfiguration])
@EnableAutoConfiguration
@EnableDiscoveryClient
static class Config {
@Bean
TestingServer testingServer() {
return new TestingServer(SocketUtils.findAvailableTcpPort())
}
@Bean
ZookeeperProperties zookeeperProperties() {
return new ZookeeperProperties(connectString: testingServer().connectString)
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {

View File

@@ -0,0 +1,4 @@
stubrunner:
camel.enabled: false
idsToServiceIds:
fraudDetectionServer: someNameThatShouldMapFraudDetectionServer

View File

@@ -0,0 +1,25 @@
<!--
~ Copyright 2013-2016 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.
-->
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.springframework.cloud" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>bootService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<metadata>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>bootService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>fraudDetectionServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<metadata>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>fraudDetectionServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>loanIssuance</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2016 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.
-->
<metadata>
<groupId>org.springframework.cloud.contract.verifier.stubs</groupId>
<artifactId>loanIssuance</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062111</lastUpdated>
</versioning>
</metadata>