Fixing missing context-path usage

without this change when the user adds context-path it gets ignored and WireMock fails to register stubs
with this change we update the WireMock instance to include the context path

fixes #99
This commit is contained in:
Marcin Grzejszczak
2016-10-11 11:27:10 +02:00
parent 4313717e01
commit b17238ff4e
26 changed files with 498 additions and 29 deletions

View File

@@ -632,7 +632,7 @@ The aforementioned contract is an agreement between two sides that:
- if an HTTP request is sent with
** a method `PUT` on an endpoint `/fraudcheck`
** JSON body with `clientId` matching the regular expression `[0-9]{10}` and `loanAmount` equal to `99999`
** JSON body with `clientPesel` matching the regular expression `[0-9]{10}` and `loanAmount` equal to `99999`
** and with a header `Content-Type` equal to `application/vnd.fraud.v1+json`
- then an HTTP response would be sent to the consumer that
** has status `200`
@@ -875,7 +875,7 @@ public void validate_shouldMarkClientAsFraud() throws Exception {
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/vnd.fraud.v1+json")
.body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}");
.body("{\"clientPesel\":\"1234567890\",\"loanAmount\":99999}");
// when:
ResponseOptions response = given().spec(request)

View File

@@ -201,7 +201,7 @@ The aforementioned contract is an agreement between two sides that:
- if an HTTP request is sent with
** a method `PUT` on an endpoint `/fraudcheck`
** JSON body with `clientId` matching the regular expression `[0-9]{10}` and `loanAmount` equal to `99999`
** JSON body with `clientPesel` matching the regular expression `[0-9]{10}` and `loanAmount` equal to `99999`
** and with a header `Content-Type` equal to `application/vnd.fraud.v1+json`
- then an HTTP response would be sent to the consumer that
** has status `200`
@@ -375,7 +375,7 @@ public void validate_shouldMarkClientAsFraud() throws Exception {
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/vnd.fraud.v1+json")
.body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}");
.body("{\"clientPesel\":\"1234567890\",\"loanAmount\":99999}");
// when:
ResponseOptions response = given().spec(request)

View File

@@ -285,7 +285,7 @@ class LoanApplicationServiceSpec extends Specification {
def 'should successfully apply for loan'() {
given:
LoanApplication application =
new LoanApplication(client: new Client(pesel: '12345678901'), amount: 123.123)
new LoanApplication(client: new Client(clientPesel: '12345678901'), amount: 123.123)
when:
LoanApplicationResult loanApplication == sut.loanApplication(application)
then:

View File

@@ -196,7 +196,7 @@ class StubRunnerExecutor implements StubFinder {
return condition ? this.stubServer.getStubUrl() : null;
}
private void startStubServers(StubRunnerOptions stubRunnerOptions,
private void startStubServers(final StubRunnerOptions stubRunnerOptions,
final StubConfiguration stubConfiguration, StubRepository repository) {
final List<WiremockMappingDescriptor> mappings = repository
.getProjectDescriptors();
@@ -206,7 +206,7 @@ class StubRunnerExecutor implements StubFinder {
if (log.isDebugEnabled()) {
log.debug("There are no HTTP related contracts. Won't start any servers");
}
this.stubServer = new StubServer(stubConfiguration, mappings, contracts, new NoOpHttpServerStub());
this.stubServer = new StubServer(stubRunnerOptions, stubConfiguration, mappings, contracts, new NoOpHttpServerStub());
return;
}
if (contracts.isEmpty()) {
@@ -215,14 +215,14 @@ class StubRunnerExecutor implements StubFinder {
+ "that's why will start the server - maybe you know what you're doing...");
}
if (port != null && port >= 0) {
this.stubServer = new StubServer(stubConfiguration, mappings, contracts,
this.stubServer = new StubServer(stubRunnerOptions, stubConfiguration, mappings, contracts,
new WireMockHttpServerStub(port));
} else {
this.stubServer = this.portScanner
.tryToExecuteWithFreePort(new PortCallback<StubServer>() {
@Override
public StubServer call(int availablePort) {
return new StubServer(stubConfiguration,
return new StubServer(stubRunnerOptions, stubConfiguration,
mappings, contracts,
new WireMockHttpServerStub(availablePort));
}

View File

@@ -61,6 +61,11 @@ public class StubRunnerOptions {
*/
final Map<StubConfiguration, Integer> stubIdsToPortMapping;
/**
* Context Path of the server
*/
final String contextPath;
public StubRunnerOptions(Integer minPortValue, Integer maxPortValue,
String stubRepositoryRoot, boolean workOffline, String stubsClassifier,
Collection<StubConfiguration> dependencies,
@@ -72,6 +77,21 @@ public class StubRunnerOptions {
this.stubsClassifier = stubsClassifier;
this.dependencies = dependencies;
this.stubIdsToPortMapping = stubIdsToPortMapping;
this.contextPath = "";
}
public StubRunnerOptions(Integer minPortValue, Integer maxPortValue,
String stubRepositoryRoot, boolean workOffline, String stubsClassifier,
Collection<StubConfiguration> dependencies,
Map<StubConfiguration, Integer> stubIdsToPortMapping, String contextPath) {
this.minPortValue = minPortValue;
this.maxPortValue = maxPortValue;
this.stubRepositoryRoot = stubRepositoryRoot;
this.workOffline = workOffline;
this.stubsClassifier = stubsClassifier;
this.dependencies = dependencies;
this.stubIdsToPortMapping = stubIdsToPortMapping;
this.contextPath = contextPath;
}
public Integer port(StubConfiguration stubConfiguration) {
@@ -90,18 +110,6 @@ public class StubRunnerOptions {
return this.maxPortValue;
}
public String getStubRepositoryRoot() {
return this.stubRepositoryRoot;
}
public boolean isWorkOffline() {
return this.workOffline;
}
public String getStubsClassifier() {
return this.stubsClassifier;
}
public Collection<StubConfiguration> getDependencies() {
return this.dependencies;
}

View File

@@ -37,6 +37,7 @@ public class StubRunnerOptionsBuilder {
private String stubRepositoryRoot;
private boolean workOffline = false;
private String stubsClassifier = "stubs";
private String contextPath = "";
public StubRunnerOptionsBuilder() {
}
@@ -95,18 +96,25 @@ public class StubRunnerOptionsBuilder {
return this;
}
public StubRunnerOptionsBuilder withContextPath(String contextPath) {
this.contextPath = contextPath;
return this;
}
public StubRunnerOptionsBuilder withOptions(StubRunnerOptions options) {
this.minPortValue = options.minPortValue;
this.maxPortValue = options.maxPortValue;
this.stubRepositoryRoot = options.stubRepositoryRoot;
this.workOffline = options.workOffline;
this.stubsClassifier = options.stubsClassifier;
this.contextPath = options.contextPath;
return this;
}
public StubRunnerOptions build() {
return new StubRunnerOptions(this.minPortValue, this.maxPortValue, this.stubRepositoryRoot,
this.workOffline, this.stubsClassifier, buildDependencies(), this.stubIdsToPortMapping);
this.workOffline, this.stubsClassifier, buildDependencies(), this.stubIdsToPortMapping,
this.contextPath);
}
private Collection<StubConfiguration> buildDependencies() {

View File

@@ -23,6 +23,7 @@ import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.util.StringUtils;
import com.github.tomakehurst.wiremock.client.WireMock;
@@ -34,9 +35,11 @@ class StubServer {
final StubConfiguration stubConfiguration;
final Collection<WiremockMappingDescriptor> mappings;
final Collection<Contract> contracts;
private final StubRunnerOptions stubRunnerOptions;
StubServer(StubConfiguration stubConfiguration, Collection<WiremockMappingDescriptor> mappings,
Collection<Contract> contracts, HttpServerStub httpServerStub) {
StubServer(StubRunnerOptions stubRunnerOptions, StubConfiguration stubConfiguration,
Collection<WiremockMappingDescriptor> mappings, Collection<Contract> contracts, HttpServerStub httpServerStub) {
this.stubRunnerOptions = stubRunnerOptions;
this.stubConfiguration = stubConfiguration;
this.mappings = mappings;
this.httpServerStub = httpServerStub;
@@ -68,13 +71,23 @@ class StubServer {
public URL getStubUrl() {
try {
return new URL("http://localhost:" + getPort());
return new URL("http://localhost:" + getPort() + prependSlashIfNecessary(this.stubRunnerOptions.contextPath));
}
catch (MalformedURLException e) {
throw new IllegalStateException("Cannot parse URL", e);
}
}
private String prependSlashIfNecessary(String contextPath) {
if (!StringUtils.hasText(contextPath)) {
return "";
}
if (contextPath.startsWith("/")) {
return contextPath;
}
return "/" + contextPath;
}
public StubConfiguration getStubConfiguration() {
return this.stubConfiguration;
}
@@ -84,7 +97,7 @@ class StubServer {
}
private void registerStubMappings() {
WireMock wireMock = new WireMock("localhost", this.httpServerStub.port());
WireMock wireMock = new WireMock("localhost", this.httpServerStub.port(), prependSlashIfNecessary(this.stubRunnerOptions.contextPath));
registerDefaultHealthChecks(wireMock);
registerStubs(this.mappings, wireMock);
}

View File

@@ -20,6 +20,7 @@ import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.contract.stubrunner.AetherStubDownloader;
import org.springframework.cloud.contract.stubrunner.BatchStubRunner;
@@ -48,6 +49,8 @@ public class StubRunnerConfiguration {
private StubDownloader stubDownloader;
@Autowired
private StubRunnerProperties props;
@Autowired(required = false)
private ServerProperties serverProperties;
/**
* Bean that initializes stub runners, runs them and on shutdown closes them. Upon its
@@ -63,7 +66,9 @@ public class StubRunnerConfiguration {
.withWorkOffline(this.props.getRepositoryRoot() == null
|| this.props.isWorkOffline())
.withStubsClassifier(this.props.getClassifier())
.withStubs(this.props.getIds()).build();
.withStubs(this.props.getIds())
.withContextPath(contextPath())
.build();
BatchStubRunner batchStubRunner = new BatchStubRunnerFactory(stubRunnerOptions,
this.stubDownloader != null ? this.stubDownloader
: new AetherStubDownloader(stubRunnerOptions),
@@ -78,4 +83,11 @@ public class StubRunnerConfiguration {
return stubRepositoryRoot != null ? stubRepositoryRoot.getURI().toString() : "";
}
private String contextPath() {
if (this.serverProperties == null) {
return "";
}
return this.serverProperties.getContextPath();
}
}

View File

@@ -28,7 +28,7 @@ class StubServerSpec extends Specification {
def 'should register stub mappings upon server start'() {
given:
List<WiremockMappingDescriptor> mappingDescriptors = new StubRepository(repository).getProjectDescriptors()
StubServer pingStubServer = new StubServer(stubConfiguration, mappingDescriptors, [],
StubServer pingStubServer = new StubServer(new TestStubRunnerOptions(), stubConfiguration, mappingDescriptors, [],
new WireMockHttpServerStub(STUB_SERVER_PORT))
when:
pingStubServer.start()
@@ -40,7 +40,7 @@ class StubServerSpec extends Specification {
def 'should provide stub server URL'() {
given:
List<WiremockMappingDescriptor> mappingDescriptors = new StubRepository(repository).getProjectDescriptors()
StubServer pingStubServer = new StubServer(stubConfiguration, mappingDescriptors, [],
StubServer pingStubServer = new StubServer(new TestStubRunnerOptions(), stubConfiguration, mappingDescriptors, [],
new WireMockHttpServerStub(STUB_SERVER_PORT))
when:
pingStubServer.start()

View File

@@ -0,0 +1,13 @@
package org.springframework.cloud.contract.stubrunner
import groovy.transform.PackageScope
/**
* @author Marcin Grzejszczak
*/
@PackageScope class TestStubRunnerOptions extends StubRunnerOptions {
public TestStubRunnerOptions() {
super(1, 2, "", false, "", new ArrayList<StubConfiguration>(),
new HashMap<StubConfiguration, Integer>());
}
}

View File

@@ -25,6 +25,7 @@
<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-context-path</module>
<module>spring-cloud-contract-stub-runner-integration</module>
<module>spring-cloud-contract-stub-runner-stream</module>
</modules>

View File

@@ -0,0 +1,45 @@
<?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.1.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-stub-runner-context-path</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Contract Stub Runner With Context Path</name>
<description>Spring Cloud Contract Stub Runner With Context Path</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,13 @@
package com.example.loan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,70 @@
package com.example.loan;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.example.loan.model.FraudCheckStatus;
import com.example.loan.model.FraudServiceRequest;
import com.example.loan.model.FraudServiceResponse;
import com.example.loan.model.LoanApplication;
import com.example.loan.model.LoanApplicationResult;
import com.example.loan.model.LoanApplicationStatus;
@Service
public class LoanApplicationService {
private static final String FRAUD_SERVICE_JSON_VERSION_1 =
"application/vnd.fraud.v1+json";
private final RestTemplate restTemplate;
private String fraudUrl = "http://localhost:8080";
public LoanApplicationService() {
this.restTemplate = new RestTemplate();
}
public LoanApplicationResult loanApplication(LoanApplication loanApplication) {
FraudServiceRequest request =
new FraudServiceRequest(loanApplication);
FraudServiceResponse response =
sendRequestToFraudDetectionService(request);
return buildResponseFromFraudResult(response);
}
private FraudServiceResponse sendRequestToFraudDetectionService(
FraudServiceRequest request) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, FRAUD_SERVICE_JSON_VERSION_1);
// tag::client_call_server[]
ResponseEntity<FraudServiceResponse> response =
this.restTemplate.exchange(this.fraudUrl + "/fraudcheck", HttpMethod.PUT,
new HttpEntity<>(request, httpHeaders),
FraudServiceResponse.class);
// end::client_call_server[]
return response.getBody();
}
private LoanApplicationResult buildResponseFromFraudResult(FraudServiceResponse response) {
LoanApplicationStatus applicationStatus = null;
if (FraudCheckStatus.OK == response.getFraudCheckStatus()) {
applicationStatus = LoanApplicationStatus.LOAN_APPLIED;
} else if (FraudCheckStatus.FRAUD == response.getFraudCheckStatus()) {
applicationStatus = LoanApplicationStatus.LOAN_APPLICATION_REJECTED;
}
return new LoanApplicationResult(applicationStatus, response.getRejectionReason());
}
public void setFraudUrl(String fraudUrl) {
this.fraudUrl = fraudUrl;
}
}

View File

@@ -0,0 +1,21 @@
package com.example.loan.model;
public class Client {
private String clientId;
public Client() {
}
public Client(String clientId) {
this.clientId = clientId;
}
public String getClientId() {
return this.clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
}

View File

@@ -0,0 +1,5 @@
package com.example.loan.model;
public enum FraudCheckStatus {
OK, FRAUD
}

View File

@@ -0,0 +1,34 @@
package com.example.loan.model;
import java.math.BigDecimal;
public class FraudServiceRequest {
private String clientPesel;
private BigDecimal loanAmount;
public FraudServiceRequest() {
}
public FraudServiceRequest(LoanApplication loanApplication) {
this.clientPesel = loanApplication.getClient().getClientId();
this.loanAmount = loanApplication.getAmount();
}
public String getClientPesel() {
return this.clientPesel;
}
public void setClientPesel(String clientPesel) {
this.clientPesel = clientPesel;
}
public BigDecimal getLoanAmount() {
return this.loanAmount;
}
public void setLoanAmount(BigDecimal loanAmount) {
this.loanAmount = loanAmount;
}
}

View File

@@ -0,0 +1,27 @@
package com.example.loan.model;
public class FraudServiceResponse {
private FraudCheckStatus fraudCheckStatus;
private String rejectionReason;
public FraudServiceResponse() {
}
public FraudCheckStatus getFraudCheckStatus() {
return this.fraudCheckStatus;
}
public void setFraudCheckStatus(FraudCheckStatus fraudCheckStatus) {
this.fraudCheckStatus = fraudCheckStatus;
}
public String getRejectionReason() {
return this.rejectionReason;
}
public void setRejectionReason(String rejectionReason) {
this.rejectionReason = rejectionReason;
}
}

View File

@@ -0,0 +1,44 @@
package com.example.loan.model;
import java.math.BigDecimal;
public class LoanApplication {
private Client client;
private BigDecimal amount;
private String loanApplicationId;
public LoanApplication() {
}
public LoanApplication(Client client, double amount) {
this.client = client;
this.amount = BigDecimal.valueOf(amount);
}
public Client getClient() {
return this.client;
}
public void setClient(Client client) {
this.client = client;
}
public BigDecimal getAmount() {
return this.amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getLoanApplicationId() {
return this.loanApplicationId;
}
public void setLoanApplicationId(String loanApplicationId) {
this.loanApplicationId = loanApplicationId;
}
}

View File

@@ -0,0 +1,32 @@
package com.example.loan.model;
public class LoanApplicationResult {
private LoanApplicationStatus loanApplicationStatus;
private String rejectionReason;
public LoanApplicationResult() {
}
public LoanApplicationResult(LoanApplicationStatus loanApplicationStatus, String rejectionReason) {
this.loanApplicationStatus = loanApplicationStatus;
this.rejectionReason = rejectionReason;
}
public LoanApplicationStatus getLoanApplicationStatus() {
return this.loanApplicationStatus;
}
public void setLoanApplicationStatus(LoanApplicationStatus loanApplicationStatus) {
this.loanApplicationStatus = loanApplicationStatus;
}
public String getRejectionReason() {
return this.rejectionReason;
}
public void setRejectionReason(String rejectionReason) {
this.rejectionReason = rejectionReason;
}
}

View File

@@ -0,0 +1,5 @@
package com.example.loan.model;
public enum LoanApplicationStatus {
LOAN_APPLIED, LOAN_APPLICATION_REJECTED
}

View File

@@ -0,0 +1,2 @@
server.port: 0
server.context-path: /my-path

View File

@@ -0,0 +1,63 @@
package com.example.loan;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.StubFinder;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.loan.model.Client;
import com.example.loan.model.LoanApplication;
import com.example.loan.model.LoanApplicationResult;
import com.example.loan.model.LoanApplicationStatus;
import static org.assertj.core.api.Assertions.assertThat;
// tag::autoconfigure_stubrunner[]
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureStubRunner(repositoryRoot = "classpath:m2repo/repository/",
ids = { "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer" })
public class LoanApplicationServiceTests {
// end::autoconfigure_stubrunner[]
@Autowired private LoanApplicationService service;
@Autowired private StubFinder stubFinder;
@Before
public void setPort() {
this.service.setFraudUrl(this.stubFinder.findStubUrl("fraudDetectionServer").toString());
}
@Test
public void shouldSuccessfullyApplyForLoan() {
// given:
LoanApplication application = new LoanApplication(new Client("1234567890"),
123.123);
// when:
LoanApplicationResult loanApplication = service.loanApplication(application);
// then:
assertThat(loanApplication.getLoanApplicationStatus())
.isEqualTo(LoanApplicationStatus.LOAN_APPLIED);
assertThat(loanApplication.getRejectionReason()).isNull();
}
// tag::client_tdd[]
@Test
public void shouldBeRejectedDueToAbnormalLoanAmount() {
// given:
LoanApplication application = new LoanApplication(new Client("1234567890"),
99999);
// when:
LoanApplicationResult loanApplication = service.loanApplication(application);
// then:
assertThat(loanApplication.getLoanApplicationStatus())
.isEqualTo(LoanApplicationStatus.LOAN_APPLICATION_REJECTED);
assertThat(loanApplication.getRejectionReason()).isEqualTo("Amount too high");
}
// end::client_tdd[]
}

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>