Compare commits

..

No commits in common. "dev" and "http-mock-server-2.2.0" have entirely different histories.

47 changed files with 511 additions and 2326 deletions

5
.gitignore vendored
View file

@ -1,5 +0,0 @@
*.iml
target/
.idea
.mvn/wrapper/maven-wrapper.jar

View file

@ -1,18 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar

5
.travis.yml Normal file
View file

@ -0,0 +1,5 @@
language: groovy
jdk:
- oraclejdk8

View file

@ -1,59 +0,0 @@
variables:
&maven_image maven:3.9.6-eclipse-temurin-11-alpine
when:
evaluate: 'not (CI_COMMIT_MESSAGE contains "Release")'
steps:
- name: build
image: *maven_image
commands:
- mvn -B clean install -DskipTests -Dmaven.test.skip
- name: test
image: *maven_image
commands:
- mvn -B -pl :mockserver-tests verify
- name: deploy to public
image: *maven_image
commands:
- mvn -B jar:jar deploy:deploy
secrets: [reposilite_user, reposilite_token]
when:
branch: [dev, master]
- name: deploy to releases
image: woodpeckerci/plugin-gitea-release
settings:
base-url: https://git.ztsh.eu
files:
- "mockserver-client/target/mockserver-client*.jar"
- "mockserver/target/mockserver-full.jar"
api_key:
from_secret: git_pat
when:
- event: tag
- name: tag docker image
image: woodpeckerci/plugin-docker-buildx
settings:
platforms: linux/amd64,linux/arm64/v8,linux/ppc64le,linux/s390x
repo: ztsheu/http-mock-server
registry: docker.io
tags: ${CI_COMMIT_TAG}
username: ztsheu
password:
from_secret: docker_pat
when:
- event: tag
- name: build docker image
image: woodpeckerci/plugin-docker-buildx
settings:
platforms: linux/amd64,linux/arm64/v8,linux/ppc64le,linux/s390x
repo: ztsheu/http-mock-server
registry: docker.io
tags: latest
username: ztsheu
password:
from_secret: docker_pat
when:
- event: tag
- event: push
branch: dev

View file

@ -1,4 +1,4 @@
FROM eclipse-temurin:11.0.22_7-jre-jammy
FROM java:8
ADD mockserver/target/mockserver-full.jar /mockserver.jar
@ -8,4 +8,4 @@ RUN mkdir /externalSchema
VOLUME /externalSchema
CMD java -cp /mockserver.jar:/externalSchema eu.ztsh.mockserver.server.Main
CMD java -cp /mockserver.jar:/externalSchema -jar /mockserver.jar

257
README.md
View file

@ -1,89 +1,25 @@
[![Build Status](https://img.shields.io/travis/TouK/http-mock-server/master.svg?style=flat)](https://travis-ci.org/TouK/http-mock-server)
HTTP MOCK SERVER
================
# HTTP MOCK SERVER
Http Mock Server allows to mock HTTP request using groovy closures.
Create server jar
-----------------
## Create server jar
```
cd mockserver
mvn clean package assembly:single
```
Start server
------------
## Start server
### Native start
```
java -jar mockserver-full.jar [PORT] [CONFIGURATION_FILE]
java -jar mockserver.jar [PORT]
```
Default port is 9999.
If configuration file is passed then port must be defined.
Configuration file is groovy configuration script e.g. :
```groovy
testRest2 {
port=9998
response='{ req -> \'<response/>\' }'
responseHeaders='{ _ -> [a: "b"] }'
path='testEndpoint'
predicate='{ req -> req.xml.name() == \'request1\'}'
name='testRest2'
}
testRest4 {
soap=true
port=9999
path='testEndpoint'
name='testRest4'
method='PUT'
statusCode=204
}
testRest3 {
port=9999
path='testEndpoint2'
name='testRest3'
}
testRest6 {
port=9999
path='testEndpoint2'
name='testRest6'
maxUses=1
cyclic=true
}
testRest {
imports {
aaa='bbb'
ccc='bla'
}
port=10001
path='testEndpoint'
name='testRest'
}
testHttps {
soap=false
port=10443
path='testHttps'
name='testHttps'
method='GET'
https={
keystorePath='/tmp/keystore.jks'
keystorePassword='keystorePass'
keyPassword='keyPass'
truststorePath='/tmp/truststore.jks'
truststorePassword='truststorePass'
requireClientAuth=true
}
}
```
### Build with docker
### Start with docker
Docker and docker-compose is needed.
@ -92,12 +28,7 @@ Docker and docker-compose is needed.
docker-compose up -d
```
### Docker repoository
Currently unavailable
Create mock on server
---------------------
## Create mock on server
### Via client
@ -113,26 +44,16 @@ remoteMockServer.addMock(new AddMock(
statusCode: ...,
method: ...,
responseHeaders: ...,
schema: ...,
maxUses: ...,
cyclic: ...,
https: new Https(
keystorePath: '/tmp/keystore.jks',
keystorePassword: 'keystorePass',
keyPassword: 'keyPass',
truststorePath: '/tmp/truststore.jks',
truststorePassword: 'truststorePass',
requireClientAuth: true
)
schema: ...
))
```
### Via HTTP
Send POST request to localhost:<PORT>/serverControl
```xml
<addMock xmlns="http://ztsh.eu/mockserver/api/request">
<addMock xmlns="http://touk.pl/mockserver/api/request">
<name>...</name>
<path>...</path>
<port>...</port>
@ -143,75 +64,47 @@ Send POST request to localhost:<PORT>/serverControl
<method>...</method>
<responseHeaders>...</responseHeaders>
<schema>...</schema>
<imports alias="..." fullClassName="..."/>
<maxUses>...</maxUses>
<cyclic>...</cyclic>
<https>
<keystorePath>/tmp/keystore.jks</keystorePath>
<keystorePassword>keystorePass</keystorePassword>
<keyPassword>keyPass</keyPassword>
<truststorePath>/tmp/truststore.jks</truststorePath>
<truststorePassword>truststorePass</truststorePassword>
<requireClientAuth>true</requireClientAuth>
</https>
</addMock>
```
### Parameters
- name - name of mock, must be unique
- path - path on which mock should be created
- port - inteer, port on which mock should be created, cannot be the same as mock server port
- predicate - groovy closure as string which must evaluate to true, when request object will be given to satisfy mock, optional, default {_ -> true}
- response - groovy closure as string which must evaluate to string which will be response of mock when predicate is satisfied, optional, default { _ -> '' }
- soap - true or false, is request and response should be wrapped in soap Envelope and Body elements, default false
- statusCode - integer, status code of response when predicate is satisfied, default 200
- method - POST|PUT|DELETE|GET|TRACE|OPTION|HEAD|ANY_METHOD, expected http method of request, default `POST`, `ANY_METHOD` matches all HTTP methods
- responseHeaders - groovyClosure as string which must evaluate to Map which will be added to response headers, default { _ -> \[:] }
- schema - path to xsd schema file on mockserver classpath; default empty, so no vallidation of request is performed; if validation fails then response has got status 400 and response is raw message from validator
- imports - list of imports for closures (each import is separate tag); `alias` is the name of `fullClassName` available in closure; `fullClassName` must be available on classpath of mock server
- https - HTTPS configuration
- maxUses - limit uses of mock to the specific number, after that mock is marked as ignored (any negative number means unlimited - default, cannot set value to 0), after this number of invocation mock history is still available, but mock does not apply to any request
- cyclic - should mock be added after `maxUses` uses at the end of the mock list (by default false)
#### HTTPS configuration
- keystorePath - path to keystore in JKS format, keystore should contains only one privateKeyEntry
- keystorePassword - keystore password
- keyPassword - key password
- truststorePath - path to truststore in JKS format
- truststorePassword - truststore password
- requireClientAuth - whether client auth is required (two-way SSL)
**HTTP** and **HTTPS** should be started on separated ports.
* name - name of mock, must be unique
* path - path on which mock should be created
* port - inteer, port on which mock should be created, cannot be the same as mock server port
* predicate - groovy closure as string which must evaluate to true, when request object will be given to satisfy mock, optional, default {_ -> true}
* response - groovy closure as string which must evaluate to string which will be response of mock when predicate is satisfied, optional, default { _ -> '' }
* soap - true or false, is request and response should be wrapped in soap Envelope and Body elements, default false
* statusCode - integer, status code of response when predicate is satisfied, default 200
* method - POST|PUT|DELETE|GET|TRACE|OPTION|HEAD, expected http method of request, default POST
* responseHeaders - groovyClosure as string which must evaluate to Map which will be added to response headers, default { _ -> [:] }
* schema - path to xsd schema file on mockserver classpath; default empty, so no vallidation of request is performed; if validation fails then response has got status 400 and response is raw message from validator
### Closures request properties
In closures input parameter (called req) contains properties:
- text - request body as java.util.String
- headers - java.util.Map with request headers
- query - java.util.Map with query parameters
- xml - groovy.util.slurpersupport.GPathResult created from request body (if request body is valid xml)
- soap - groovy.util.slurpersupport.GPathResult created from request body without Envelope and Body elements (if request body is valid soap xml)
- json - java.lang.Object created from request body (if request body is valid json)
- path - java.util.List<String> with not empty parts of request path
* text - request body as java.util.String
* headers - java.util.Map with request headers
* query - java.util.Map with query parameters
* xml - groovy.util.slurpersupport.GPathResult created from request body (if request body is valid xml)
* soap - groovy.util.slurpersupport.GPathResult created from request body without Envelope and Body elements (if request body is valid soap xml)
* json - java.lang.Object created from request body (if request body is valid json)
* path - java.util.List<String> with not empty parts of request path
Response if success:
```xml
<mockAdded xmlns="http://ztsh.eu/mockserver/api/response"/>
<mockAdded xmlns="http://touk.pl/mockserver/api/response"/>
```
Response with error message if failure:
```xml
<exceptionOccured xmlns="http://ztsh.eu/mockserver/api/response">...</exceptionOccured>
<exceptionOccured xmlns="http://touk.pl/mockserver/api/response">...</exceptionOccured>
```
Peek mock
---------
## Peek mock
Mock could be peeked to get get report of its invocations.
### Via client
@ -221,11 +114,10 @@ List<MockEvent> mockEvents = remoteMockServer.peekMock('...')
```
### Via HTTP
Send POST request to localhost:<PORT>/serverControl
```xml
<peekMock xmlns="http://ztsh.eu/mockserver/api/request">
<peekMock xmlns="http://touk.pl/mockserver/api/request">
<name>...</name>
</peekMock>
```
@ -233,7 +125,7 @@ Send POST request to localhost:<PORT>/serverControl
Response if success:
```xml
<mockPeeked xmlns="http://ztsh.eu/mockserver/api/response">
<mockPeeked xmlns="http://touk.pl/mockserver/api/response">
<mockEvent>
<request>
<text>...</text>
@ -265,11 +157,10 @@ Response if success:
Response with error message if failure:
```xml
<exceptionOccured xmlns="http://ztsh.eu/mockserver/api/response">...</exceptionOccured>
<exceptionOccured xmlns="http://touk.pl/mockserver/api/response">...</exceptionOccured>
```
Remove mock
-----------
## Remove mock
When mock was used it could be unregistered by name. It also optionally returns report of mock invocations if second parameter is true.
@ -278,13 +169,11 @@ When mock was used it could be unregistered by name. It also optionally returns
```java
List<MockEvent> mockEvents = remoteMockServer.removeMock('...', ...)
```
### Via HTTP
Send POST request to localhost:<PORT>/serverControl
```xml
<removeMock xmlns="http://ztsh.eu/mockserver/api/request">
<removeMock xmlns="http://touk.pl/mockserver/api/request">
<name>...</name>
<skipReport>...</skipReport>
</removeMock>
@ -293,7 +182,7 @@ Send POST request to localhost:<PORT>/serverControl
Response if success (and skipReport not given or equal false):
```xml
<mockRemoved xmlns="http://ztsh.eu/mockserver/api/response">
<mockRemoved xmlns="http://touk.pl/mockserver/api/response">
<mockEvent>
<request>
<text>...</text>
@ -325,17 +214,16 @@ Response if success (and skipReport not given or equal false):
If skipReport is set to true then response will be:
```xml
<mockRemoved xmlns="http://ztsh.eu/mockserver/api/response"/>
<mockRemoved xmlns="http://touk.pl/mockserver/api/response"/>
```
Response with error message if failure:
```xml
<exceptionOccured xmlns="http://ztsh.eu/mockserver/api/response">...</exceptionOccured>
<exceptionOccured xmlns="http://touk.pl/mockserver/api/response">...</exceptionOccured>
```
List mocks definitions
----------------------
## List mocks definitions
### Via client
@ -361,71 +249,14 @@ Response:
<soap>...</soap>
<method>...</method>
<statusCode>...</statusCode>
<imports alias="..." fullClassName="..."/>
</mock>
...
</mocks>
```
Get mocks configuration
-----------------------
## Remote repository
### Via client
```java
ConfigObject mocks = remoteMockServer.getConfiguration()
```
### Via HTTP
Send GET request to localhost:<PORT>/serverControl/configuration
Response:
```groovy
testRest2 {
port=9998
response='{ req -> \'<response/>\' }'
responseHeaders='{ _ -> [a: "b"] }'
path='testEndpoint'
predicate='{ req -> req.xml.name() == \'request1\'}'
name='testRest2'
}
testRest4 {
soap=true
port=9999
path='testEndpoint'
name='testRest4'
method='PUT'
statusCode=204
}
testRest3 {
port=9999
path='testEndpoint2'
name='testRest3'
}
testRest6 {
port=9999
path='testEndpoint2'
name='testRest6'
}
testRest {
imports {
aaa='bbb'
ccc='bla'
}
port=10001
path='testEndpoint'
name='testRest'
}
```
This response could be saved to file and passed as it is during mock server creation.
Remote repository
-----------------
Mockserver is available at `philanthropist.ztsh.eu`.
Mockserver is available at `philanthropist.touk.pl`.
Just add repository to maven pom:
@ -436,16 +267,10 @@ Just add repository to maven pom:
...
<repository>
<id>touk</id>
<url>https://philanthropist.ztsh.eu/nexus/content/repositories/releases</url>
<url>https://philanthropist.touk.pl/nexus/content/repositories/releases</url>
</repository>
...
</repositories>
...
</project>
```
FAQ
---
Q: *Can I have two mocks returning responses interchangeably for the same request?*
A: Yes, you can. Just set two mocks with `maxUses: 1` and `cyclic: true`.

View file

@ -1,21 +1,15 @@
<?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>
<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">
<parent>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>http-mock-server</artifactId>
<version>3.0.0-SNAPSHOT</version>
<groupId>pl.touk.mockserver</groupId>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mockserver-api</artifactId>
<dependencies>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -24,9 +18,19 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>xjc</id>

View file

@ -1,9 +1,10 @@
<bindings version="3.0"
xmlns="https://jakarta.ee/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc">
<globalBindings>
<xjc:simple/>
</globalBindings>
</bindings>
<?xml version="1.0"?>
<jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xjc= "http://java.sun.com/xml/ns/jaxb/xjc"
jxb:extensionBindingPrefixes="xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jxb:bindings>
<jxb:globalBindings>
<xjc:simple/>
</jxb:globalBindings>
</jxb:bindings>
</jxb:bindings>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://ztsh.eu/mockserver/api/common" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:schema version="1.0" targetNamespace="http://touk.pl/mockserver/api/common" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="method">
<xs:restriction base="xs:string">
@ -11,7 +11,6 @@
<xs:enumeration value="HEAD"/>
<xs:enumeration value="OPTIONS"/>
<xs:enumeration value="PATCH"/>
<xs:enumeration value="ANY_METHOD" />
</xs:restriction>
</xs:simpleType>
@ -19,16 +18,5 @@
<xs:attribute name="alias" type="xs:string"/>
<xs:attribute name="fullClassName" type="xs:string"/>
</xs:complexType>
<xs:complexType name="https">
<xs:sequence>
<xs:element name="keystorePath" type="xs:string" />
<xs:element name="keystorePassword" type="xs:string" />
<xs:element name="keyPassword" type="xs:string" />
<xs:element name="truststorePath" type="xs:string" />
<xs:element name="truststorePassword" type="xs:string" />
<xs:element name="requireClientAuth" type="xs:boolean" />
</xs:sequence>
</xs:complexType>
</xs:schema>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema elementFormDefault="qualified" version="1.0" targetNamespace="http://ztsh.eu/mockserver/api/request" xmlns:tns="http://ztsh.eu/mockserver/api/request" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:common="http://ztsh.eu/mockserver/api/common">
<xs:schema elementFormDefault="qualified" version="1.0" targetNamespace="http://touk.pl/mockserver/api/request" xmlns:tns="http://touk.pl/mockserver/api/request" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:common="http://touk.pl/mockserver/api/common">
<xs:import namespace="http://ztsh.eu/mockserver/api/common" schemaLocation="common.xsd"/>
<xs:import namespace="http://touk.pl/mockserver/api/common" schemaLocation="common.xsd"/>
<xs:element name="addMock" type="tns:AddMock"/>
@ -20,13 +20,9 @@
<xs:element name="soap" type="xs:boolean" minOccurs="0"/>
<xs:element name="statusCode" type="xs:int" minOccurs="0"/>
<xs:element name="method" type="common:method" minOccurs="0"/>
<xs:element name="https" type="common:https" minOccurs="0" />
<xs:element name="responseHeaders" type="xs:string" minOccurs="0"/>
<xs:element name="schema" type="xs:string" minOccurs="0"/>
<xs:element name="imports" type="common:importAlias" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="preserveHistory" type="xs:boolean" minOccurs="0"/>
<xs:element name="maxUses" type="xs:int" minOccurs="0" />
<xs:element name="cyclic" type="xs:boolean" minOccurs="0" default="false" />
</xs:sequence>
</xs:extension>
</xs:complexContent>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema elementFormDefault="qualified" version="1.0" targetNamespace="http://ztsh.eu/mockserver/api/response" xmlns:tns="http://ztsh.eu/mockserver/api/response" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:common="http://ztsh.eu/mockserver/api/common">
<xs:schema elementFormDefault="qualified" version="1.0" targetNamespace="http://touk.pl/mockserver/api/response" xmlns:tns="http://touk.pl/mockserver/api/response" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:common="http://touk.pl/mockserver/api/common">
<xs:import namespace="http://ztsh.eu/mockserver/api/common" schemaLocation="common.xsd"/>
<xs:import namespace="http://touk.pl/mockserver/api/common" schemaLocation="common.xsd"/>
<xs:element name="exceptionOccured" type="tns:exceptionOccured"/>
@ -110,7 +110,6 @@
<xs:element name="statusCode" type="xs:int"/>
<xs:element name="schema" type="xs:string" minOccurs="0"/>
<xs:element name="imports" type="common:importAlias" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="preserveHistory" type="xs:boolean" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

View file

@ -1,44 +1,22 @@
<?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>
<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">
<parent>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>http-mock-server</artifactId>
<version>3.0.0-SNAPSHOT</version>
<groupId>pl.touk.mockserver</groupId>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mockserver-client</artifactId>
<build>
<defaultGoal>clean install</defaultGoal>
</build>
<dependencies>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-api</artifactId>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-json</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-xml</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-core</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
@ -47,15 +25,9 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>pl.touk.mockserver</groupId>
<artifactId>mockserver-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,4 +1,4 @@
package eu.ztsh.mockserver.client
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked

View file

@ -1,4 +1,4 @@
package eu.ztsh.mockserver.client
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked

View file

@ -1,4 +1,4 @@
package eu.ztsh.mockserver.client
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked

View file

@ -1,4 +1,4 @@
package eu.ztsh.mockserver.client
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked

View file

@ -1,4 +1,4 @@
package eu.ztsh.mockserver.client
package pl.touk.mockserver.client
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpGet
@ -7,17 +7,13 @@ import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import eu.ztsh.mockserver.api.request.AddMock
import eu.ztsh.mockserver.api.request.MockServerRequest
import eu.ztsh.mockserver.api.request.PeekMock
import eu.ztsh.mockserver.api.request.RemoveMock
import eu.ztsh.mockserver.api.response.MockEventReport
import eu.ztsh.mockserver.api.response.MockPeeked
import eu.ztsh.mockserver.api.response.MockRemoved
import eu.ztsh.mockserver.api.response.MockReport
import eu.ztsh.mockserver.api.response.Mocks
import pl.touk.mockserver.api.request.AddMock
import pl.touk.mockserver.api.request.MockServerRequest
import pl.touk.mockserver.api.request.PeekMock
import pl.touk.mockserver.api.request.RemoveMock
import pl.touk.mockserver.api.response.*
import jakarta.xml.bind.JAXBContext
import javax.xml.bind.JAXBContext
class RemoteMockServer {
private final String address
@ -51,13 +47,6 @@ class RemoteMockServer {
return mockPeeked.mockEvents ?: []
}
ConfigObject getConfiguration() {
HttpGet get = new HttpGet(address + '/configuration')
CloseableHttpResponse response = client.execute(get)
String configuration = Util.extractStringResponse(response)
return new ConfigSlurper().parse(configuration)
}
private static StringEntity buildRemoveMockRequest(RemoveMock data) {
return new StringEntity(marshallRequest(data), ContentType.create("text/xml", "UTF-8"))
}

View file

@ -1,18 +1,17 @@
package eu.ztsh.mockserver.client
package pl.touk.mockserver.client
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked
import groovy.xml.XmlSlurper
import groovy.xml.slurpersupport.GPathResult
import groovy.util.slurpersupport.GPathResult
import org.apache.http.HttpEntity
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.util.EntityUtils
import eu.ztsh.mockserver.api.response.ExceptionOccured
import eu.ztsh.mockserver.api.response.MockAdded
import eu.ztsh.mockserver.api.response.MockServerResponse
import pl.touk.mockserver.api.response.ExceptionOccured
import pl.touk.mockserver.api.response.MockAdded
import pl.touk.mockserver.api.response.MockServerResponse
import jakarta.xml.bind.JAXBContext
import javax.xml.bind.JAXBContext
@CompileStatic
@TypeChecked

View file

@ -1,68 +1,49 @@
<?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>
<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">
<parent>
<artifactId>http-mock-server</artifactId>
<groupId>eu.ztsh.mockserver</groupId>
<version>3.0.0-SNAPSHOT</version>
<groupId>pl.touk.mockserver</groupId>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mockserver-tests</artifactId>
<build>
<defaultGoal>clean install</defaultGoal>
</build>
<dependencies>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver</artifactId>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</dependency>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-client</artifactId>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>pl.touk.mockserver</groupId>
<artifactId>mockserver</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>pl.touk.mockserver</groupId>
<artifactId>mockserver-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,156 +0,0 @@
package eu.ztsh.mockserver.tests
import eu.ztsh.mockserver.api.common.Https
import eu.ztsh.mockserver.api.request.AddMock
import eu.ztsh.mockserver.client.RemoteMockServer
import eu.ztsh.mockserver.client.Util
import eu.ztsh.mockserver.server.HttpMockServer
import groovy.xml.slurpersupport.GPathResult
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpPost
import org.apache.http.conn.ssl.SSLConnectionSocketFactory
import org.apache.http.conn.ssl.SSLContexts
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import spock.lang.AutoCleanup
import spock.lang.Ignore
import spock.lang.Shared
import spock.lang.Specification
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLHandshakeException
import java.security.KeyStore
@Ignore
class MockServerHttpsTest extends Specification {
RemoteMockServer remoteMockServer = new RemoteMockServer('localhost', 19000)
@AutoCleanup('stop')
HttpMockServer httpMockServer = new HttpMockServer(19000)
@Shared
SSLContext noClientAuthSslContext = SSLContexts.custom()
.loadTrustMaterial(trustStore())
.build()
@Shared
SSLContext trustedCertificateSslContext = SSLContexts.custom()
.loadKeyMaterial(trustedCertificateKeystore(), 'changeit'.toCharArray())
.loadTrustMaterial(trustStore())
.build()
@Shared
SSLContext untrustedCertificateSslContext = SSLContexts.custom()
.loadKeyMaterial(untrustedCertificateKeystore(), 'changeit'.toCharArray())
.loadTrustMaterial(trustStore())
.build()
@Ignore("TODO: SSL peer shut down incorrectly")
def 'should handle HTTPS server' () {
given:
remoteMockServer.addMock(new AddMock(
name: 'testHttps',
path: 'testEndpoint',
port: 10443,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> "<goodResponse-${req.xml.name()}/>"}''',
https: new Https(
keyPassword: 'changeit',
keystorePassword: 'changeit',
keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path
),
soap: false
))
when:
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client(noClientAuthSslContext).execute(restPost)
then:
GPathResult restPostResponse = Util.extractXmlResponse(response)
restPostResponse.name() == 'goodResponse-request'
}
@Ignore("TODO: SSL peer shut down incorrectly")
def 'should handle HTTPS server with client auth' () {
given:
remoteMockServer.addMock(new AddMock(
name: 'testHttps',
path: 'testEndpoint',
port: 10443,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> "<goodResponse-${req.xml.name()}/>"}''',
https: new Https(
keyPassword: 'changeit',
keystorePassword: 'changeit',
keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path,
truststorePath: MockServerHttpsTest.classLoader.getResource('truststore.jks').path,
truststorePassword: 'changeit',
requireClientAuth: true
),
soap: false
))
when:
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client(trustedCertificateSslContext).execute(restPost)
then:
GPathResult restPostResponse = Util.extractXmlResponse(response)
restPostResponse.name() == 'goodResponse-request'
}
def 'should handle HTTPS server with wrong client auth' () {
given:
remoteMockServer.addMock(new AddMock(
name: 'testHttps',
path: 'testEndpoint',
port: 10443,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> "<goodResponse-${req.xml.name()}/>"}''',
https: new Https(
keyPassword: 'changeit',
keystorePassword: 'changeit',
keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path,
truststorePath: MockServerHttpsTest.classLoader.getResource('truststore.jks').path,
truststorePassword: 'changeit',
requireClientAuth: true
),
soap: false
))
when:
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
client(sslContext).execute(restPost)
then:
thrown(SSLHandshakeException)
where:
sslContext << [noClientAuthSslContext, untrustedCertificateSslContext]
}
private CloseableHttpClient client(SSLContext sslContext) {
return HttpClients.custom()
.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.setSslcontext(sslContext)
.build()
}
private KeyStore trustedCertificateKeystore() {
return loadKeystore('trusted.jks')
}
private KeyStore untrustedCertificateKeystore() {
return loadKeystore('untrusted.jks')
}
private KeyStore trustStore() {
return loadKeystore('truststore.jks')
}
private KeyStore loadKeystore(String fileName) {
KeyStore truststore = KeyStore.getInstance(KeyStore.defaultType)
truststore.load(new FileInputStream(MockServerHttpsTest.classLoader.getResource(fileName).path), "changeit".toCharArray());
return truststore
}
}

View file

@ -1,239 +0,0 @@
package eu.ztsh.mockserver.tests
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import eu.ztsh.mockserver.api.request.AddMock
import eu.ztsh.mockserver.client.RemoteMockServer
import eu.ztsh.mockserver.server.HttpMockServer
import spock.lang.AutoCleanup
import spock.lang.Specification
class MockServerMaxUsesTest extends Specification {
RemoteMockServer remoteMockServer
@AutoCleanup('stop')
HttpMockServer httpMockServer
CloseableHttpClient client = HttpClients.createDefault()
def setup() {
httpMockServer = new HttpMockServer(9000)
remoteMockServer = new RemoteMockServer('localhost', 9000)
}
def 'should return two mocks in order'() {
given:'mock with predicate is given but for only one use'
remoteMockServer.addMock(new AddMock(
name: 'mock1',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock1'}''',
maxUses: 1
))
and:'mock with the same predicate is given'
remoteMockServer.addMock(new AddMock(
name: 'mock2',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock2'}''',
))
when:'we call the first time'
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:'first mock should be returned and expired'
response.entity.content.text == 'mock1'
when:'we call the second time using the same request'
CloseableHttpResponse response2 = client.execute(restPost)
then:'second mock should be returned'
response2.entity.content.text == 'mock2'
when:'we call the third time using the same request'
CloseableHttpResponse response3 = client.execute(restPost)
then:'second mock should be returned, because it has unlimited uses'
response3.entity.content.text == 'mock2'
}
def 'should return two mocks in order but only once'() {
given:'mock with predicate is given but for only one use'
remoteMockServer.addMock(new AddMock(
name: 'mock1',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock1'}''',
maxUses: 1
))
and:'mock with the same predicate is given'
remoteMockServer.addMock(new AddMock(
name: 'mock2',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock2'}''',
maxUses: 1,
))
when:'we call the first time'
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:'first mock should be returned and expired'
response.entity.content.text == 'mock1'
when:'we call the second time using the same request'
CloseableHttpResponse response2 = client.execute(restPost)
then:'second mock should be returned'
response2.entity.content.text == 'mock2'
when:'we call the third time using the same request'
CloseableHttpResponse response3 = client.execute(restPost)
then:'no mock should be found'
response3.statusLine.statusCode == 404
and:'mock should exist'
remoteMockServer.listMocks().find { it.name == 'mock1' } != null
}
def 'should return two mocks in cyclic order'() {
given:'mock with predicate is given but for only one use'
remoteMockServer.addMock(new AddMock(
name: 'mock1',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock1'}''',
maxUses: 1,
cyclic: true,
preserveHistory: true
))
and:'mock with the same predicate is given'
remoteMockServer.addMock(new AddMock(
name: 'mock2',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock2'}''',
maxUses: 1,
cyclic: true
))
when:'we call the first time'
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:'first mock should be returned and expired'
response.entity.content.text == 'mock1'
when:'we call the second time using the same request'
CloseableHttpResponse response2 = client.execute(restPost)
then:'second mock should be returned and expired'
response2.entity.content.text == 'mock2'
when:'we call the third time using the same request'
CloseableHttpResponse response3 = client.execute(restPost)
then:'first mock should be returned, because these mocks are cyclic'
response3.entity.content.text == 'mock1'
when:'we call the fourth time using the same request'
CloseableHttpResponse response4 = client.execute(restPost)
then:'second mock should be returned, because these mocks are cyclic'
response4.entity.content.text == 'mock2'
and:
remoteMockServer.peekMock('mock1').size() == 2
}
def 'should return two mocks with the same request interjected by another'() {
given:'mock with predicate is given but for only one use'
remoteMockServer.addMock(new AddMock(
name: 'mock1',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock1'}''',
maxUses: 1,
cyclic: true
))
and:'mock with the same predicate is given'
remoteMockServer.addMock(new AddMock(
name: 'mock2',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock2'}''',
maxUses: 1,
cyclic: true
))
and:'mock with other predicate is given'
remoteMockServer.addMock(new AddMock(
name: 'otherMock',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'otherRequest'}''',
response: '''{req -> 'otherMock'}'''
))
when:'we call the first time'
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:'first mock should be returned and expired'
response.entity.content.text == 'mock1'
when:'we call other request'
HttpPost otherRestPost = new HttpPost('http://localhost:9999/testEndpoint')
otherRestPost.entity = new StringEntity('<otherRequest/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse otherResponse = client.execute(otherRestPost)
then:'other mock should be called'
otherResponse.entity.content.text == 'otherMock'
when:'we call the second time using the same request'
CloseableHttpResponse response2 = client.execute(restPost)
then:'second mock should be returned and expired'
response2.entity.content.text == 'mock2'
when:'we call the third time using the same request'
CloseableHttpResponse response3 = client.execute(restPost)
then:'first mock should be returned, because these mocks are cyclic'
response3.entity.content.text == 'mock1'
}
def 'should return first mock twice'() {
given:'mock with predicate is given but for only one use'
remoteMockServer.addMock(new AddMock(
name: 'mock1',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock1'}''',
maxUses: 2
))
and:'mock with the same predicate is given'
remoteMockServer.addMock(new AddMock(
name: 'mock2',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> 'mock2'}''',
))
when:'we call the first time'
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:'first mock should be returned and expired'
response.entity.content.text == 'mock1'
when:'we call the second time using the same request'
CloseableHttpResponse response2 = client.execute(restPost)
then:'again first mock should be returned'
response2.entity.content.text == 'mock1'
when:'we call the third time using the same request'
CloseableHttpResponse response3 = client.execute(restPost)
then:'second mock should be returned'
response3.entity.content.text == 'mock2'
}
def 'should throw exception if adding mock with incorrect maxUses'() {
when:
remoteMockServer.addMock(new AddMock(
name: 'mock1',
maxUses: 0
))
then:
thrown(RuntimeException)
}
}

View file

@ -1,6 +1,6 @@
package eu.ztsh.mockserver.tests
package pl.touk.mockserver.tests
import groovy.xml.slurpersupport.GPathResult
import groovy.util.slurpersupport.GPathResult
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpDelete
import org.apache.http.client.methods.HttpGet
@ -15,29 +15,29 @@ import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils
import eu.ztsh.mockserver.api.common.ImportAlias
import eu.ztsh.mockserver.api.common.Method
import eu.ztsh.mockserver.api.request.AddMock
import eu.ztsh.mockserver.api.response.MockEventReport
import eu.ztsh.mockserver.api.response.MockReport
import eu.ztsh.mockserver.client.InvalidMockDefinition
import eu.ztsh.mockserver.client.InvalidMockRequestSchema
import eu.ztsh.mockserver.client.MockAlreadyExists
import eu.ztsh.mockserver.client.MockDoesNotExist
import eu.ztsh.mockserver.client.RemoteMockServer
import eu.ztsh.mockserver.client.Util
import eu.ztsh.mockserver.server.HttpMockServer
import spock.lang.AutoCleanup
import spock.lang.Ignore
import pl.touk.mockserver.api.common.ImportAlias
import pl.touk.mockserver.api.common.Method
import pl.touk.mockserver.api.request.AddMock
import pl.touk.mockserver.api.response.MockEventReport
import pl.touk.mockserver.api.response.MockReport
import pl.touk.mockserver.client.InvalidMockDefinition
import pl.touk.mockserver.client.InvalidMockRequestSchema
import pl.touk.mockserver.client.MockAlreadyExists
import pl.touk.mockserver.client.MockDoesNotExist
import pl.touk.mockserver.client.RemoteMockServer
import pl.touk.mockserver.client.Util
import pl.touk.mockserver.server.HttpMockServer
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class MockServerIntegrationTest extends Specification {
RemoteMockServer remoteMockServer
@AutoCleanup('stop')
HttpMockServer httpMockServer
@Shared
CloseableHttpClient client = HttpClients.createDefault()
def setup() {
@ -45,6 +45,10 @@ class MockServerIntegrationTest extends Specification {
remoteMockServer = new RemoteMockServer('localhost', 9000)
}
def cleanup() {
httpMockServer.stop()
}
def "should add working rest mock on endpoint"() {
expect:
remoteMockServer.addMock(new AddMock(
@ -66,7 +70,6 @@ class MockServerIntegrationTest extends Specification {
remoteMockServer.removeMock('testRest')?.size() == 1
}
@Ignore("TODO: restPostResponse.name()")
def "should add working rest mock on endpoint with utf"() {
expect:
remoteMockServer.addMock(new AddMock(
@ -241,6 +244,7 @@ class MockServerIntegrationTest extends Specification {
soapPostResponse.Body.'goodResponseSoap-request'.size() == 1
}
@Unroll
def "should dispatch rest mocks when second on #name"() {
given:
remoteMockServer.addMock(new AddMock(
@ -279,6 +283,7 @@ class MockServerIntegrationTest extends Specification {
9998 | 'test2' | 'another port and path'
}
@Unroll
def "should dispatch rest mock with response code"() {
given:
remoteMockServer.addMock(new AddMock(
@ -852,6 +857,7 @@ class MockServerIntegrationTest extends Specification {
mockEvents2[0].response.statusCode == 202
}
@Unroll
def "should return mock report with #mockEvents events when deleting mock with flag skip mock = #skipReport"() {
expect:
remoteMockServer.addMock(new AddMock(
@ -879,6 +885,7 @@ class MockServerIntegrationTest extends Specification {
true | 0
}
@Unroll
def "should reject mock when it has System.exit in closure"() {
when:
remoteMockServer.addMock(new AddMock(
@ -1022,162 +1029,4 @@ class MockServerIntegrationTest extends Specification {
expect:
remoteMockServer.removeMock('testRest')?.size() == 1
}
def "should get configuration of mocks and reconfigure new mock server based on it"() {
given:
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'testEndpoint',
port: 9998,
predicate: '''{ req -> req.xml.name() == 'request1'}''',
response: '''{ req -> '<response/>' }''',
responseHeaders: '{ _ -> [a: "b"] }'
))
remoteMockServer.addMock(new AddMock(
name: 'testRest4',
path: 'testEndpoint',
port: 9999,
soap: true,
statusCode: 204,
method: Method.PUT
))
remoteMockServer.addMock(new AddMock(
name: 'testRest3',
path: 'testEndpoint2',
port: 9999
))
remoteMockServer.addMock(new AddMock(
name: 'testRest5',
path: 'testEndpoint',
port: 9999
))
remoteMockServer.addMock(new AddMock(
name: 'testRest6',
path: 'testEndpoint2',
port: 9999
))
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
schema: 'schema2.xsd',
imports: [
new ImportAlias(alias: 'aaa', fullClassName: 'bbb'),
new ImportAlias(alias: 'ccc', fullClassName: 'bla')
],
preserveHistory: true
))
remoteMockServer.removeMock('testRest5')
when:
ConfigObject configObject = remoteMockServer.configuration
httpMockServer.stop()
httpMockServer = new HttpMockServer(9000, configObject)
then:
List<MockReport> mockReport = remoteMockServer.listMocks()
mockReport.size() == 5
assertMockReport(mockReport[0], [name: 'testRest', path: 'testEndpoint', port: 9999, predicate: '{ _ -> true }', response: '''{ _ -> '' }''', responseHeaders: '{ _ -> [:] }', soap: false, statusCode: 200, method: Method.POST, schema: 'schema2.xsd', preserveHistory: true])
assertMockReport(mockReport[1], [name: 'testRest2', path: 'testEndpoint', port: 9998, predicate: '''{ req -> req.xml.name() == 'request1'}''', response: '''{ req -> '<response/>' }''', responseHeaders: '{ _ -> [a: "b"] }', soap: false, statusCode: 200, method: Method.POST])
assertMockReport(mockReport[2], [name: 'testRest3', path: 'testEndpoint2', port: 9999, predicate: '{ _ -> true }', response: '''{ _ -> '' }''', responseHeaders: '{ _ -> [:] }', soap: false, statusCode: 200, method: Method.POST])
assertMockReport(mockReport[3], [name: 'testRest4', path: 'testEndpoint', port: 9999, predicate: '{ _ -> true }', response: '''{ _ -> '' }''', responseHeaders: '{ _ -> [:] }', soap: true, statusCode: 204, method: Method.PUT])
assertMockReport(mockReport[4], [name: 'testRest6', path: 'testEndpoint2', port: 9999, predicate: '{ _ -> true }', response: '''{ _ -> '' }''', responseHeaders: '{ _ -> [:] }', soap: false, statusCode: 200, method: Method.POST])
mockReport[0].imports.find { it.alias == 'aaa' }?.fullClassName == 'bbb'
mockReport[0].imports.find { it.alias == 'ccc' }?.fullClassName == 'bla'
}
def "should add mock without history"() {
expect:
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> "<goodResponseRest-${req.xml.name()}/>"}''',
soap: false,
preserveHistory: false
))
when:
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:
GPathResult restPostResponse = Util.extractXmlResponse(response)
restPostResponse.name() == 'goodResponseRest-request'
expect:
remoteMockServer.removeMock('testRest')?.size() == 0
}
def "should handle empty post"() {
expect:
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
statusCode: 201,
soap: false
))
when:
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
CloseableHttpResponse response = client.execute(restPost)
then:
response.statusLine.statusCode == 201
Util.consumeResponse(response)
expect:
remoteMockServer.removeMock('testRest')?.size() == 1
}
def 'should handle leading slash'() {
given:
String name = "testRest-${UUID.randomUUID().toString()}"
expect:
remoteMockServer.addMock(new AddMock(
name: name,
path: mockPath,
port: 9999,
statusCode: 201,
soap: false
))
when:
HttpPost restPost = new HttpPost("http://localhost:9999/$urlPath")
CloseableHttpResponse response = client.execute(restPost)
then:
response.statusLine.statusCode == 201
Util.consumeResponse(response)
expect:
remoteMockServer.removeMock(name)?.size() == 1
where:
mockPath | urlPath
'' | ''
'/' | ''
'test' | 'test'
'/test' | 'test'
'test/other' | 'test/other'
'/test/other' | 'test/other'
}
def 'should match any method'() {
given:
String name = "testRest-${UUID.randomUUID().toString()}"
remoteMockServer.addMock(new AddMock(
name: name,
path: 'any-method',
port: 9999,
statusCode: 201,
soap: false,
method: Method.ANY_METHOD
))
when:
CloseableHttpResponse response = client.execute(req)
then:
response.statusLine.statusCode == 201
Util.consumeResponse(response)
cleanup:
remoteMockServer.removeMock(name)
where:
req << [
new HttpGet('http://localhost:9999/any-method'),
new HttpPost('http://localhost:9999/any-method'),
new HttpPatch('http://localhost:9999/any-method')
]
}
}

View file

@ -0,0 +1,59 @@
package pl.touk.mockserver.tests
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.HttpClients
import pl.touk.mockserver.api.request.AddMock
import pl.touk.mockserver.client.RemoteMockServer
import pl.touk.mockserver.client.Util
import pl.touk.mockserver.server.HttpMockServer
import spock.lang.Specification
import spock.lang.Timeout
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class ServerMockPT extends Specification {
@Timeout(value = 90)
def "should handle many request simultaneously"() {
given:
HttpClient client = HttpClients.createDefault()
HttpMockServer httpMockServer = new HttpMockServer()
RemoteMockServer controlServerClient = new RemoteMockServer("localhost", 9999)
int requestAmount = 1000
String[] responses = new String[requestAmount]
ExecutorService executorService = Executors.newCachedThreadPool()
when:
for (int i = 0; i < requestAmount; ++i) {
int current = i
executorService.submit {
int endpointNumber = current % 10
int port = 9000 + (current % 7)
controlServerClient.addMock(new AddMock(
name: "testRest$current",
path: "testEndpoint$endpointNumber",
port: port,
predicate: """{req -> req.xml.name() == 'request$current'}""",
response: """{req -> "<goodResponse$current/>"}"""
))
HttpPost restPost = new HttpPost("http://localhost:$port/testEndpoint$endpointNumber")
restPost.entity = new StringEntity("<request$current/>", ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
responses[current] = Util.extractStringResponse(response)
controlServerClient.removeMock("testRest$current", true)
}
}
executorService.shutdown()
executorService.awaitTermination(90, TimeUnit.SECONDS)
then:
responses.eachWithIndex { res, i -> assert res && new XmlSlurper().parseText(res).name() == "goodResponse$i" as String }
cleanup:
executorService.shutdown()
httpMockServer.stop()
}
}

View file

@ -1,76 +1,46 @@
<?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>
<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">
<parent>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>http-mock-server</artifactId>
<version>3.0.0-SNAPSHOT</version>
<groupId>pl.touk.mockserver</groupId>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mockserver</artifactId>
<dependencies>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-api</artifactId>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-json</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-xml</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-core</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>pl.touk.mockserver</groupId>
<artifactId>mockserver-api</artifactId>
</dependency>
</dependencies>
<build>
<defaultGoal>clean package assembly:single install</defaultGoal>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>eu.ztsh.mockserver.server.Main</mainClass>
<mainClass>pl.touk.mockserver.server.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
@ -79,17 +49,7 @@
<finalName>mockserver-full</finalName>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>create-archive</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,112 +0,0 @@
package eu.ztsh.mockserver.server
import com.sun.net.httpserver.HttpExchange
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import eu.ztsh.mockserver.api.common.Method
import java.util.concurrent.CopyOnWriteArrayList
@Slf4j
@PackageScope
class ContextExecutor {
private final HttpServerWrapper httpServerWrapper
final String path
private final List<Mock> mocks
ContextExecutor(HttpServerWrapper httpServerWrapper, Mock initialMock) {
this.httpServerWrapper = httpServerWrapper
this.path = "/${initialMock.path}"
this.mocks = new CopyOnWriteArrayList<>([initialMock])
httpServerWrapper.createContext(path) {
HttpExchange ex ->
try {
applyMocks(ex)
} catch (Exception e) {
log.error("Exceptiony occured handling request", e)
throw e
} finally {
ex.close()
}
}
}
private void applyMocks(HttpExchange ex) {
MockRequest request = new MockRequest(ex.requestBody.text, ex.requestHeaders, ex.requestURI)
log.info('Mock received input')
log.debug("Request: ${request.text}")
for (Mock mock : mocks) {
try {
if (mock.match(Method.valueOf(ex.requestMethod), request)) {
log.debug("Mock ${mock.name} match request ${request.text}")
handleMaxUses(mock)
MockResponse httpResponse = mock.apply(request)
fillExchange(ex, httpResponse)
log.trace("Mock ${mock.name} response with body ${httpResponse.text}")
return
}
log.debug("Mock ${mock.name} does not match request")
} catch (Exception e) {
log.warn("An exception occured when matching or applying mock ${mock.name}", e)
}
}
log.warn("Any mock does not match request ${request.text}")
Util.createResponse(ex, request.text, 404)
}
String getPath() {
return path.substring(1)
}
String getContextPath() {
return path
}
private static void fillExchange(HttpExchange httpExchange, MockResponse response) {
response.headers.each {
httpExchange.responseHeaders.add(it.key, it.value)
}
Util.createResponse(httpExchange, response.text, response.statusCode)
}
List<MockEvent> removeMock(String name) {
Mock mock = mocks.find { it.name == name }
if (mock) {
mocks.remove(mock)
return mock.history
}
return []
}
List<MockEvent> peekMock(String name) {
Mock mock = mocks.find { it.name == name }
if (mock) {
return mock.history
}
return []
}
void addMock(Mock mock) {
mocks << mock
}
List<Mock> getMocks() {
return mocks
}
private synchronized void handleMaxUses(Mock mock) {
if (mock.hasLimitedUses()) {
mock.decrementUses()
resetIfNeeded(mock)
log.debug("Uses left ${mock.usesLeft} of ${mock.maxUses} (is cyclic: ${mock.cyclic})")
}
}
private void resetIfNeeded(Mock mock) {
if (mock.shouldUsesBeReset()) {
mock.resetUses()
mocks.remove(mock)
mocks.add(mock)
}
}
}

View file

@ -1,106 +0,0 @@
package eu.ztsh.mockserver.server
import com.sun.net.httpserver.HttpHandler
import com.sun.net.httpserver.HttpServer
import com.sun.net.httpserver.HttpsServer
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import eu.ztsh.mockserver.api.common.Https
import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import java.security.KeyStore
import java.security.SecureRandom
import java.util.concurrent.Executor
@Slf4j
@PackageScope
class HttpServerWrapper {
private final HttpServer httpServer
final int port
private List<ContextExecutor> executors = []
HttpServerWrapper(int port, Executor executor, Https https = null) {
this.port = port
InetSocketAddress addr = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), port)
httpServer = buildServer(addr, https)
httpServer.executor = executor
log.info("Http server starting on port $port...")
httpServer.start()
log.info('Http server is started')
}
private HttpServer buildServer(InetSocketAddress addr, Https https) {
if (https) {
HttpsServer httpsServer = HttpsServer.create(addr, 0)
httpsServer.httpsConfigurator = new HttpsConfig(buildSslContext(https), https)
return httpsServer
} else {
return HttpServer.create(addr, 0)
}
}
private SSLContext buildSslContext(Https https) {
KeyManager[] keyManagers = buildKeyManager(https)
TrustManager[] trustManagers = buildTrustManager(https)
SSLContext ssl = SSLContext.getInstance('TLSv1')
ssl.init(keyManagers, trustManagers, new SecureRandom())
return ssl
}
private KeyManager[] buildKeyManager(Https https) {
KeyStore keyStore = KeyStore.getInstance('jks')
keyStore.load(new FileInputStream(https.keystorePath), https.keystorePassword.toCharArray())
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.defaultAlgorithm)
kmf.init(keyStore, https.keyPassword.toCharArray())
return kmf.keyManagers
}
private TrustManager[] buildTrustManager(Https https) {
if (https.requireClientAuth) {
KeyStore trustStore = KeyStore.getInstance('jks')
trustStore.load(new FileInputStream(https.truststorePath), https.truststorePassword.toCharArray())
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.defaultAlgorithm)
tmf.init(trustStore)
return tmf.trustManagers
} else {
return []
}
}
void createContext(String context, HttpHandler handler) {
httpServer.createContext(context, handler)
}
void addMock(Mock mock) {
ContextExecutor executor = executors.find { it.path == mock.path }
if (executor) {
executor.addMock(mock)
} else {
executors << new ContextExecutor(this, mock)
}
log.info("Added mock ${mock.name}")
}
void stop() {
executors.each { httpServer.removeContext(it.contextPath) }
httpServer.stop(0)
}
List<MockEvent> removeMock(String name) {
return executors.collect { it.removeMock(name) }.flatten() as List<MockEvent>
}
List<MockEvent> peekMock(String name) {
return executors.collect { it.peekMock(name) }.flatten() as List<MockEvent>
}
List<Mock> getMocks() {
return executors.collect { it.mocks }.flatten() as List<Mock>
}
}

View file

@ -1,28 +0,0 @@
package eu.ztsh.mockserver.server
import com.sun.net.httpserver.HttpsConfigurator
import com.sun.net.httpserver.HttpsParameters
import groovy.transform.CompileStatic
import eu.ztsh.mockserver.api.common.Https
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLParameters
@CompileStatic
class HttpsConfig extends HttpsConfigurator {
private final Https https
HttpsConfig(SSLContext sslContext, Https https) {
super(sslContext)
this.https = https
}
@Override
void configure(HttpsParameters httpsParameters) {
SSLContext sslContext = getSSLContext()
SSLParameters sslParameters = sslContext.defaultSSLParameters
sslParameters.needClientAuth = https.requireClientAuth
httpsParameters.needClientAuth = https.requireClientAuth
httpsParameters.SSLParameters = sslParameters
}
}

View file

@ -1,33 +0,0 @@
package eu.ztsh.mockserver.server
import groovy.util.logging.Slf4j
@Slf4j
class Main {
static void main(String[] args) {
HttpMockServer httpMockServer = startMockServer(args)
Runtime.runtime.addShutdownHook(new Thread({
log.info('Http server is stopping...')
httpMockServer.stop()
log.info('Http server is stopped')
} as Runnable))
while (true) {
Thread.sleep(10000)
}
}
private static HttpMockServer startMockServer(String... args) {
switch (args.length) {
case 1:
return new HttpMockServer(args[0] as int, new ConfigObject())
case 2:
return new HttpMockServer(args[0] as int, new ConfigSlurper().parse(new File(args[1]).toURI().toURL()))
case 3:
return new HttpMockServer(args[0] as int, new ConfigSlurper().parse(new File(args[1]).toURI().toURL()), args[2] as int)
default:
return new HttpMockServer()
}
}
}

View file

@ -0,0 +1,84 @@
package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpExchange
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import pl.touk.mockserver.api.common.Method
import java.util.concurrent.CopyOnWriteArrayList
@Slf4j
@PackageScope
class ContextExecutor {
private final HttpServerWraper httpServerWraper
final String path
private final List<Mock> mocks
ContextExecutor(HttpServerWraper httpServerWraper, Mock initialMock) {
this.httpServerWraper = httpServerWraper
this.path = "/${initialMock.path}"
this.mocks = new CopyOnWriteArrayList<>([initialMock])
httpServerWraper.createContext(path) {
HttpExchange ex ->
MockRequest request = new MockRequest(ex.requestBody.text, ex.requestHeaders, ex.requestURI)
log.info('Mock received input')
log.debug("Request: ${request.text}")
for (Mock mock : mocks) {
try {
if (mock.match(Method.valueOf(ex.requestMethod), request)) {
log.debug("Mock ${mock.name} match request ${request.text}")
MockResponse httpResponse = mock.apply(request)
fillExchange(ex, httpResponse)
log.trace("Mock ${mock.name} response with body ${httpResponse.text}")
return
}
log.debug("Mock ${mock.name} does not match request")
} catch (Exception e) {
log.warn("An exception occured when matching or applying mock ${mock.name}", e)
}
}
log.warn("Any mock does not match request ${request.text}")
Util.createResponse(ex, request.text, 404)
}
}
String getPath() {
return path.substring(1)
}
String getContextPath() {
return path
}
private static void fillExchange(HttpExchange httpExchange, MockResponse response) {
response.headers.each {
httpExchange.responseHeaders.add(it.key, it.value)
}
Util.createResponse(httpExchange, response.text, response.statusCode)
}
List<MockEvent> removeMock(String name) {
Mock mock = mocks.find { it.name == name }
if (mock) {
mocks.remove(mock)
return mock.history
}
return []
}
List<MockEvent> peekMock(String name) {
Mock mock = mocks.find { it.name == name }
if (mock) {
return mock.history
}
return []
}
void addMock(Mock mock) {
mocks << mock
}
List<Mock> getMocks() {
return mocks
}
}

View file

@ -1,62 +1,47 @@
package eu.ztsh.mockserver.server
package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpExchange
import groovy.util.logging.Slf4j
import eu.ztsh.mockserver.api.common.Https
import eu.ztsh.mockserver.api.common.ImportAlias
import eu.ztsh.mockserver.api.common.Method
import eu.ztsh.mockserver.api.request.AddMock
import eu.ztsh.mockserver.api.request.MockServerRequest
import eu.ztsh.mockserver.api.request.PeekMock
import eu.ztsh.mockserver.api.request.RemoveMock
import eu.ztsh.mockserver.api.response.ExceptionOccured
import eu.ztsh.mockserver.api.response.MockAdded
import eu.ztsh.mockserver.api.response.MockEventReport
import eu.ztsh.mockserver.api.response.MockPeeked
import eu.ztsh.mockserver.api.response.MockRemoved
import eu.ztsh.mockserver.api.response.MockReport
import eu.ztsh.mockserver.api.response.MockRequestReport
import eu.ztsh.mockserver.api.response.MockResponseReport
import eu.ztsh.mockserver.api.response.Mocks
import eu.ztsh.mockserver.api.response.Parameter
import pl.touk.mockserver.api.common.ImportAlias
import pl.touk.mockserver.api.request.AddMock
import pl.touk.mockserver.api.request.MockServerRequest
import pl.touk.mockserver.api.request.PeekMock
import pl.touk.mockserver.api.request.RemoveMock
import pl.touk.mockserver.api.response.ExceptionOccured
import pl.touk.mockserver.api.response.MockAdded
import pl.touk.mockserver.api.response.MockEventReport
import pl.touk.mockserver.api.response.MockPeeked
import pl.touk.mockserver.api.response.MockRemoved
import pl.touk.mockserver.api.response.MockReport
import pl.touk.mockserver.api.response.MockRequestReport
import pl.touk.mockserver.api.response.MockResponseReport
import pl.touk.mockserver.api.response.Mocks
import pl.touk.mockserver.api.response.Parameter
import jakarta.xml.bind.JAXBContext
import javax.xml.bind.JAXBContext
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import static eu.ztsh.mockserver.server.Util.createResponse
import static pl.touk.mockserver.server.Util.createResponse
@Slf4j
class HttpMockServer {
private final HttpServerWrapper httpServerWrapper
private final Map<Integer, HttpServerWrapper> childServers = new ConcurrentHashMap<>()
private final HttpServerWraper httpServerWraper
private final Map<Integer, HttpServerWraper> childServers = new ConcurrentHashMap<>()
private final Set<String> mockNames = new CopyOnWriteArraySet<>()
private final ConfigObject configuration = new ConfigObject()
private final Executor executor
private static
final JAXBContext requestJaxbContext = JAXBContext.newInstance(AddMock.package.name, AddMock.classLoader)
HttpMockServer(int port = 9999, ConfigObject initialConfiguration = new ConfigObject(), int threads = 10) {
executor = Executors.newFixedThreadPool(threads)
httpServerWrapper = new HttpServerWrapper(port, executor)
HttpMockServer(int port = 9999) {
httpServerWraper = new HttpServerWraper(port)
initialConfiguration.values()?.each { ConfigObject co ->
addMock(co)
}
httpServerWrapper.createContext('/serverControl', {
httpServerWraper.createContext('/serverControl', {
HttpExchange ex ->
try {
if (ex.requestMethod == 'GET') {
if (ex.requestURI.path == '/serverControl/configuration') {
createResponse(ex, configuration.prettyPrint(), 200)
} else {
listMocks(ex)
}
listMocks(ex)
} else if (ex.requestMethod == 'POST') {
MockServerRequest request = requestJaxbContext.createUnmarshaller().unmarshal(ex.requestBody) as MockServerRequest
if (request instanceof AddMock) {
@ -91,8 +76,7 @@ class HttpMockServer {
method: it.method,
statusCode: it.statusCode as int,
schema: it.schema,
imports: it.imports.collect { new ImportAlias(alias: it.key, fullClassName: it.value) },
preserveHistory: it.preserveHistory
imports: it.imports.collect { new ImportAlias(alias: it.key, fullClassName: it.value) }
)
}
)
@ -108,50 +92,13 @@ class HttpMockServer {
if (name in mockNames) {
throw new RuntimeException('mock already registered')
}
if (request.maxUses == 0) {
throw new RuntimeException('cannot set maxUses to 0')
}
Mock mock = mockFromRequest(request)
HttpServerWrapper child = getOrCreateChildServer(mock.port, mock.https)
HttpServerWraper child = getOrCreateChildServer(mock.port)
child.addMock(mock)
saveConfiguration(request)
mockNames << name
createResponse(ex, new MockAdded(), 200)
}
private void addMock(ConfigObject co) {
String name = co.name
if (name in mockNames) {
throw new RuntimeException('mock already registered')
}
if (co.maxUses == 0) {
throw new RuntimeException('cannot set maxUses to 0')
}
Mock mock = mockFromConfig(co)
HttpServerWrapper child = getOrCreateChildServer(mock.port, mock.https)
child.addMock(mock)
configuration.put(name, co)
mockNames << name
}
private void saveConfiguration(AddMock request) {
ConfigObject mockDefinition = new ConfigObject()
request.metaPropertyValues.findAll { it.name != 'class' && it.value }.each {
if (it.name == 'imports') {
ConfigObject configObject = new ConfigObject()
it.value.each { ImportAlias imp ->
configObject.put(imp.alias, imp.fullClassName)
}
mockDefinition.put(it.name, configObject)
} else if (it.name == 'method') {
mockDefinition.put(it.name, it.value.name())
} else {
mockDefinition.put(it.name, it.value)
}
}
configuration.put(request.name, mockDefinition)
}
private static Mock mockFromRequest(AddMock request) {
Mock mock = new Mock(request.name, request.path, request.port)
mock.imports = request.imports?.collectEntries { [(it.alias): it.fullClassName] } ?: [:]
@ -162,43 +109,13 @@ class HttpMockServer {
mock.method = request.method
mock.responseHeaders = request.responseHeaders
mock.schema = request.schema
mock.preserveHistory = request.preserveHistory != false
mock.https = request.https
mock.maxUses = request.maxUses
mock.cyclic = request.cyclic
return mock
}
private static Mock mockFromConfig(ConfigObject co) {
Mock mock = new Mock(co.name, co.path, co.port)
mock.imports = co.imports
mock.predicate = co.predicate ?: null
mock.response = co.response ?: null
mock.soap = co.soap ?: null
mock.statusCode = co.statusCode ?: null
mock.method = co.method ? Method.valueOf(co.method) : null
mock.responseHeaders = co.responseHeaders ?: null
mock.schema = co.schema ?: null
mock.preserveHistory = co.preserveHistory != false
if (co.https) {
mock.https = new Https(
keystorePath: co.https.keystorePath ?: null,
keystorePassword: co.https.keystorePassword,
keyPassword: co.https.keyPassword,
truststorePath: co.https.truststorePath,
truststorePassword: co.https.truststorePassword,
requireClientAuth: co.https?.requireClientAuth?.asBoolean() ?: false
)
}
mock.maxUses = co.maxUses ?: null
mock.cyclic = co.cyclic ?: null
return mock
}
private HttpServerWrapper getOrCreateChildServer(int mockPort, Https https) {
HttpServerWrapper child = childServers[mockPort]
private HttpServerWraper getOrCreateChildServer(int mockPort) {
HttpServerWraper child = childServers[mockPort]
if (!child) {
child = new HttpServerWrapper(mockPort, executor, https)
child = new HttpServerWraper(mockPort)
childServers.put(mockPort, child)
}
return child
@ -215,7 +132,6 @@ class HttpMockServer {
it.removeMock(name)
}.flatten() as List<MockEvent>
mockNames.remove(name)
configuration.remove(name)
MockRemoved mockRemoved = new MockRemoved(
mockEvents: createMockEventReports(mockEvents)
)
@ -266,6 +182,6 @@ class HttpMockServer {
void stop() {
childServers.values().each { it.stop() }
httpServerWrapper.stop()
httpServerWraper.stop()
}
}

View file

@ -0,0 +1,58 @@
package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpHandler
import com.sun.net.httpserver.HttpServer
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import java.util.concurrent.Executors
@Slf4j
@PackageScope
class HttpServerWraper {
private final HttpServer httpServer
final int port
private List<ContextExecutor> executors = []
HttpServerWraper(int port) {
this.port = port
InetSocketAddress addr = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), port)
httpServer = HttpServer.create(addr, 0)
httpServer.executor = Executors.newWorkStealingPool()
log.info("Http server starting on port $port...")
httpServer.start()
log.info('Http server is started')
}
void createContext(String context, HttpHandler handler) {
httpServer.createContext(context, handler)
}
void addMock(Mock mock) {
ContextExecutor executor = executors.find { it.path == mock.path }
if (executor) {
executor.addMock(mock)
} else {
executors << new ContextExecutor(this, mock)
}
log.info("Added mock ${mock.name}")
}
void stop() {
executors.each { httpServer.removeContext(it.contextPath) }
httpServer.stop(0)
}
List<MockEvent> removeMock(String name) {
return executors.collect { it.removeMock(name) }.flatten() as List<MockEvent>
}
List<MockEvent> peekMock(String name) {
return executors.collect { it.peekMock(name) }.flatten() as List<MockEvent>
}
List<Mock> getMocks() {
return executors.collect { it.mocks }.flatten() as List<Mock>
}
}

View file

@ -0,0 +1,20 @@
package pl.touk.mockserver.server
import groovy.util.logging.Slf4j
@Slf4j
class Main {
static void main(String[] args) {
HttpMockServer httpMockServer = args.length == 1 ? new HttpMockServer(args[0] as int) : new HttpMockServer()
Runtime.runtime.addShutdownHook(new Thread({
log.info('Http server is stopping...')
httpMockServer.stop()
log.info('Http server is stopped')
} as Runnable))
while (true) {
Thread.sleep(10000)
}
}
}

View file

@ -1,12 +1,11 @@
package eu.ztsh.mockserver.server
package pl.touk.mockserver.server
import groovy.transform.EqualsAndHashCode
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
import eu.ztsh.mockserver.api.common.Https
import eu.ztsh.mockserver.api.common.Method
import pl.touk.mockserver.api.common.Method
import javax.xml.XMLConstants
import javax.xml.transform.stream.StreamSource
@ -35,32 +34,18 @@ class Mock implements Comparable<Mock> {
String schema
private Validator validator
Map<String, String> imports = [:]
boolean preserveHistory = true
Https https
int maxUses = -1
int usesLeft
boolean cyclic
Mock(String name, String path, int port) {
if (!(name)) {
throw new RuntimeException("Mock name must be given")
}
this.name = name
this.path = stripLeadingSlash(path)
this.path = path
this.port = port
}
private static String stripLeadingSlash(String path) {
if (path?.startsWith('/')) {
return path - '/'
} else {
return path
}
}
boolean match(Method method, MockRequest request) {
boolean usesCondition = hasLimitedUses() ? usesLeft > 0 : true
return usesCondition && (this.method == method || this.method == Method.ANY_METHOD) && predicate(request)
return this.method == method && predicate(request)
}
MockResponse apply(MockRequest request) {
@ -75,9 +60,7 @@ class Mock implements Comparable<Mock> {
}
} catch (Exception e) {
MockResponse response = new MockResponse(400, e.message, [:])
if(preserveHistory) {
history << new MockEvent(request, response)
}
history << new MockEvent(request, response)
return response
}
}
@ -86,9 +69,7 @@ class Mock implements Comparable<Mock> {
String response = soap ? wrapSoap(responseText) : responseText
Map<String, String> headers = responseHeaders(request)
MockResponse mockResponse = new MockResponse(statusCode, response, headers)
if(preserveHistory) {
history << new MockEvent(request, mockResponse)
}
history << new MockEvent(request, mockResponse)
return mockResponse
}
@ -117,9 +98,7 @@ class Mock implements Comparable<Mock> {
}
compilerConfiguration.addCompilationCustomizers(customizer)
GroovyShell sh = new GroovyShell(this.class.classLoader, compilerConfiguration);
Closure closure = sh.evaluate(predicate) as Closure
sh.resetLoadedClasses()
return closure
return sh.evaluate(predicate) as Closure
}
void setResponse(String response) {
@ -152,17 +131,6 @@ class Mock implements Comparable<Mock> {
}
}
void setMaxUses(Integer maxUses) {
if (maxUses > 0) {
this.maxUses = maxUses
this.usesLeft = maxUses
}
}
void setCyclic(Boolean cyclic) {
this.cyclic = cyclic ?: false
}
@Override
int compareTo(Mock o) {
return name.compareTo(o.name)
@ -173,27 +141,11 @@ class Mock implements Comparable<Mock> {
if (schema) {
try {
validator = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
.newSchema(this.class.getResource("/$schema"))
.newSchema(new File(this.class.getResource("/$schema").path))
.newValidator()
} catch (Exception e) {
throw new RuntimeException('mock request schema is invalid schema', e)
}
}
}
boolean hasLimitedUses() {
return maxUses > 0
}
void decrementUses() {
usesLeft--
}
boolean shouldUsesBeReset() {
return hasLimitedUses() && usesLeft <= 0 && cyclic
}
void resetUses() {
setMaxUses(maxUses)
}
}

View file

@ -1,4 +1,4 @@
package eu.ztsh.mockserver.server
package pl.touk.mockserver.server
import groovy.transform.PackageScope

View file

@ -1,10 +1,9 @@
package eu.ztsh.mockserver.server
package pl.touk.mockserver.server
import com.sun.net.httpserver.Headers
import groovy.json.JsonSlurper
import groovy.transform.PackageScope
import groovy.xml.XmlSlurper
import groovy.xml.slurpersupport.GPathResult
import groovy.util.slurpersupport.GPathResult
import groovy.xml.XmlUtil
@PackageScope
@ -28,9 +27,6 @@ class MockRequest {
}
private static GPathResult inputToXml(String text) {
if (!text.startsWith('<')) {
return null
}
try {
return new XmlSlurper().parseText(text)
} catch (Exception _) {
@ -40,7 +36,7 @@ class MockRequest {
private static GPathResult inputToSoap(GPathResult xml) {
try {
if (xml != null && xml.name() == 'Envelope' && xml.Body.size() > 0) {
if (xml.name() == 'Envelope' && xml.Body.size() > 0) {
return getSoapBodyContent(xml)
} else {
return null
@ -55,9 +51,6 @@ class MockRequest {
}
private static Object inputToJson(String text) {
if (!text.startsWith('[') && !text.startsWith('{')) {
return null
}
try {
return new JsonSlurper().parseText(text)
} catch (Exception _) {

View file

@ -1,4 +1,4 @@
package eu.ztsh.mockserver.server
package pl.touk.mockserver.server
import groovy.transform.PackageScope

View file

@ -1,9 +1,9 @@
package eu.ztsh.mockserver.server
package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpExchange
import eu.ztsh.mockserver.api.response.MockAdded
import pl.touk.mockserver.api.response.MockAdded
import jakarta.xml.bind.JAXBContext
import javax.xml.bind.JAXBContext
class Util {

308
mvnw vendored
View file

@ -1,308 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
javaHome="$(dirname "\"$javaExecutable\"")"
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
javaHome="$(dirname "\"$javaExecutable\"")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
# Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' < "$1"
fi
}
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
fi
while IFS="=" read -r key value; do
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget > /dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

205
mvnw.cmd vendored
View file

@ -1,205 +0,0 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

View file

@ -1,70 +0,0 @@
<?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>eu.ztsh.mockserver</groupId>
<artifactId>http-mock-server</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>mockserver-performance-tests</artifactId>
<dependencies>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver</artifactId>
</dependency>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-client</artifactId>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
</dependency>
</dependencies>
<properties>
<exec-maven-plugin.version>1.4.0</exec-maven-plugin.version>
</properties>
<profiles>
<profile>
<id>performance-test</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
<executions>
<execution>
<id>run-benchmarks</id>
<phase>integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<classpathScope>test</classpathScope>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<classpath />
<argument>org.openjdk.jmh.Main</argument>
<argument>.*</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -1,82 +0,0 @@
package eu.ztsh.mockserver.client;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.infra.ThreadParams;
import eu.ztsh.mockserver.api.request.AddMock;
import eu.ztsh.mockserver.server.HttpMockServer;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.SECONDS)
public class MockserverTest {
HttpMockServer httpMockServer;
int initialPort = 9000;
@Setup
public void prepareMockServer() {
httpMockServer = new HttpMockServer(9999);
}
@TearDown
public void stopMockServer() {
httpMockServer.stop();
}
@State(Scope.Thread)
public static class TestState {
RemoteMockServer remoteMockServer;
HttpClient httpClient;
int current;
@Setup
public void prepareMockServer(ThreadParams params) {
remoteMockServer = new RemoteMockServer("localhost", 9999);
httpClient = HttpClients.createDefault();
current = params.getThreadIndex();
}
}
@Benchmark
@Measurement(iterations = 20)
@BenchmarkMode({Mode.AverageTime, Mode.Throughput, Mode.SampleTime})
@Warmup(iterations = 10)
public void shouldHandleManyRequestsSimultaneously(TestState testState, Blackhole bh) throws IOException {
int current = testState.current;
int endpointNumber = current % 10;
int port = initialPort + (current % 7);
AddMock addMock = new AddMock();
addMock.setName("testRest" + current);
addMock.setPath("testEndpoint" + endpointNumber);
addMock.setPort(port);
addMock.setPredicate("{req -> req.xml.name() == 'request" + current + "' }");
addMock.setResponse("{req -> '<goodResponse" + current + "/>'}");
testState.remoteMockServer.addMock(addMock);
HttpPost restPost = new HttpPost("http://localhost:" + port + "/testEndpoint" + endpointNumber);
restPost.setEntity(new StringEntity("<request" + current + "/>", ContentType.create("text/xml", "UTF-8")));
CloseableHttpResponse response = (CloseableHttpResponse) testState.httpClient.execute(restPost);
String stringResponse = Util.extractStringResponse(response);
testState.remoteMockServer.removeMock("testRest" + current, true);
assert stringResponse.equals("<goodResponse" + current + "/>");
bh.consume(stringResponse);
}
}

View file

@ -1,15 +0,0 @@
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%highlight(%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n)</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder><pattern>%msg%n</pattern></encoder>
</appender>
<root level="ERROR">
<appender-ref ref="CONSOLE" />
</root>
</configuration>

175
pom.xml
View file

@ -1,90 +1,63 @@
<?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">
<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>
<groupId>eu.ztsh.mockserver</groupId>
<parent>
<groupId>pl.touk</groupId>
<artifactId>top</artifactId>
<version>1.0.7</version>
</parent>
<groupId>pl.touk.mockserver</groupId>
<artifactId>http-mock-server</artifactId>
<packaging>pom</packaging>
<version>3.0.0-SNAPSHOT</version>
<version>2.2.0</version>
<modules>
<module>mockserver-client</module>
<module>mockserver</module>
<module>mockserver-tests</module>
<module>mockserver-api</module>
<module>performance-tests</module>
</modules>
<properties>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<groovy.version>4.0.12</groovy.version>
<httpclient.version>4.5.13</httpclient.version>
<spock-core.version>2.2-groovy-4.0</spock-core.version>
<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<groovy.version>2.4.1</groovy.version>
<httpclient.version>4.3.5</httpclient.version>
<spock-core.version>1.0-groovy-2.4</spock-core.version>
<commons-lang3.version>3.3.2</commons-lang3.version>
<slf4j-api.version>1.7.30</slf4j-api.version>
<logback.version>1.3.12</logback.version>
<lombok.version>1.18.26</lombok.version>
<jaxb.version>4.0.4</jaxb.version>
<slf4j-api.version>1.7.7</slf4j-api.version>
<logback-classic.version>1.0.13</logback-classic.version>
<lombok.version>1.16.6</lombok.version>
<autoVersionSubmodules>true</autoVersionSubmodules>
<jmh.version>1.37</jmh.version>
<gmavenplus-plugin.version>3.0.2</gmavenplus-plugin.version>
<jaxb2-maven-plugin.version>3.1.0</jaxb2-maven-plugin.version>
</properties>
<scm>
<connection>scm:git:git@github.com:TouK/http-mock-server.git</connection>
<developerConnection>scm:git:git@github.com:TouK/http-mock-server.git</developerConnection>
<tag>http-mock-server-2.2.0</tag>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-bom</artifactId>
<version>${jaxb.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-json</artifactId>
<version>${groovy.version}</version>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-xml</artifactId>
<version>${groovy.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>${spock-core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
@ -95,66 +68,64 @@
<artifactId>slf4j-api</artifactId>
<version>${slf4j-api.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<version>${logback-classic.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>${spock-core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
<groupId>pl.touk.mockserver</groupId>
<artifactId>mockserver-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>${jaxb2-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>${gmavenplus-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<defaultGoal>clean install</defaultGoal>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<autoversionsubmodules>true</autoversionsubmodules>
</configuration>
</plugin>
</plugins>
</build>
<contributors>
<contributor>
<name>Dominik Przybysz</name>
<email>alien11689@gmail.com</email>
</contributor>
</contributors>
<repositories>
<repository>
<id>touk</id>
<name>TouK Open source repository</name>
<url>https://philanthropist.touk.pl/nexus/content/repositories/releases</url>
</repository>
</repositories>
</project>