Compare commits

...

102 commits

Author SHA1 Message Date
55ee22c3a1
ci: secrets fix 2024-02-25 22:09:28 +01:00
65fba1eebc Merge pull request 'JDK 11 & Jakarta' (#1) from jakarta into dev
Reviewed-on: https://hattori.ztsh.eu/stawros/http-mock-server/pulls/1
2024-02-25 22:05:26 +01:00
2a11dd9b10
ci: fixed branches in reposilite deploy 2024-02-25 21:03:17 +01:00
ea16fa59d0
Jammy & multiplatform 2024-02-25 20:40:06 +01:00
1c5909cf8d
CI push tasks 2024-02-25 17:32:34 +01:00
0884505a18
CI 2024-02-23 23:32:02 +01:00
024bec4304
build: Surefire fixes 2024-02-23 00:04:20 +01:00
b2857d956b
chore: JRE in Dockerfile updated 2024-02-22 22:49:31 +01:00
5ddcb87179
feat!: Groovy 4 & JAXB 4 2024-02-22 22:48:45 +01:00
514064debc
feat!: Jakarta & JDK11 2024-02-22 22:48:02 +01:00
2fe3f2a67d
chore: Readme update 2024-02-22 22:44:02 +01:00
36bc5dd14b
Rebranding 2024-02-22 22:42:33 +01:00
Dominik Przybysz
afaab504da
Merge pull request #18 from TouK/dependabot/maven/ch.qos.logback-logback-core-1.3.12
Bump ch.qos.logback:logback-core from 1.2.9 to 1.3.12
2023-11-30 20:01:37 +01:00
dependabot[bot]
f9ae4a8003
Bump ch.qos.logback:logback-core from 1.2.9 to 1.3.12
Bumps [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) from 1.2.9 to 1.3.12.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.2.9...v_1.3.12)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-29 22:06:34 +00:00
Łukasz Bigorajski
a3a7c4f8dc [maven-release-plugin] prepare for next development iteration 2023-06-01 17:51:38 +02:00
Łukasz Bigorajski
e44c1de1a2 [maven-release-plugin] prepare release http-mock-server-2.8.4 2023-06-01 17:51:30 +02:00
Łukasz Bigorajski
3413bfa0ad [maven-release-plugin] prepare for next development iteration 2023-06-01 11:11:57 +02:00
Łukasz Bigorajski
a825ad6fa2 [maven-release-plugin] prepare release http-mock-server-2.8.3 2023-06-01 11:11:53 +02:00
Piotr Fus
374947847d Handle any method as mock request method 2023-05-28 19:46:37 +02:00
Piotr Fus
8ba339b8a0 Setup github actions 2023-05-28 19:31:25 +02:00
Piotr Fus
ab6345234f Setup maven wrapper 2023-05-28 19:19:11 +02:00
Dominik Przybysz
763f303a65
Merge pull request #13 from TouK/dependabot/maven/ch.qos.logback-logback-core-1.2.9
Bump logback-core from 1.2.3 to 1.2.9
2023-04-17 08:21:51 +02:00
dependabot[bot]
23bba7e713
Bump logback-core from 1.2.3 to 1.2.9
Bumps logback-core from 1.2.3 to 1.2.9.

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-16 12:31:25 +00:00
Dominik Przybysz
82b22565d8
Merge pull request #12 from TouK/mockserver
Update httpclient
2021-06-08 07:58:37 +02:00
Piotr Fus
1a422fefca Update httpclient 2021-06-08 07:21:32 +02:00
Dominik Przybysz
b314332228
Merge pull request #11 from TouK/logback
Update logback
2021-06-08 06:35:20 +02:00
Piotr Fus
f8f2cfb125 Update logback 2021-06-07 20:39:08 +02:00
Piotr Fus
db93a99b9a [maven-release-plugin] prepare for next development iteration 2020-10-07 14:48:56 +02:00
Piotr Fus
8824634652 [maven-release-plugin] prepare release http-mock-server-2.8.2 2020-10-07 14:48:48 +02:00
Piotr Fus
6d52f8bceb [maven-release-plugin] prepare for next development iteration 2020-10-07 14:44:38 +02:00
Dominik Przybysz
c5a9450e00
Merge pull request #7 from lukasz-bigorajski/bump.groovy.version
Bump groovy version
2020-10-07 13:43:58 +02:00
Łukasz Bigorajski
170fb58c99 Bump groovy version 2020-10-07 13:25:07 +02:00
Piotr Fus
6438c661da [maven-release-plugin] prepare for next development iteration 2020-08-12 07:30:23 +02:00
Piotr Fus
531daa0fbe [maven-release-plugin] prepare release http-mock-server-2.8.0 2020-08-12 07:30:14 +02:00
Piotr Fus
c0188689f2
Merge pull request #6 from TouK/max.uses.history
Do not remove mock from history after max uses
2020-08-11 13:34:23 +02:00
Piotr Fus
fe9ef89970 Do not remove mock from history after max uses 2020-08-11 13:25:33 +02:00
Piotr Fus
769199f5d4 [maven-release-plugin] prepare for next development iteration 2020-08-10 12:27:17 +02:00
Piotr Fus
aa3919dbd2 [maven-release-plugin] prepare release http-mock-server-2.7.0 2020-08-10 12:27:09 +02:00
Piotr Fus
080516733a
Merge pull request #5 from TouK/dependabot/maven/org.apache.httpcomponents-httpclient-4.3.6
Bump httpclient from 4.3.5 to 4.3.6
2020-08-10 12:00:02 +02:00
dependabot[bot]
2cd84e3052
Bump httpclient from 4.3.5 to 4.3.6
Bumps httpclient from 4.3.5 to 4.3.6.

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-10 09:53:13 +00:00
Piotr Fus
6044b3a275
Merge pull request #4 from TouK/max.uses.cyclic
Add limited mock uses
2020-08-10 11:52:48 +02:00
Piotr Fus
79622a4177 Add limited mock uses 2020-08-10 09:52:41 +02:00
Dominik Adam Przybysz
fcd99cf61f Build assembly by default 2019-04-04 19:42:37 +02:00
Dominik Przybysz
c118365bbe [maven-release-plugin] prepare for next development iteration 2018-09-04 13:07:27 +02:00
Dominik Przybysz
4128b8b49d [maven-release-plugin] prepare release http-mock-server-2.6.1 2018-09-04 13:07:18 +02:00
Anna Czajka
9c6afd599f Change schema resource fetching (#3) 2018-09-03 12:30:40 +02:00
Dominik Przybysz
579dd08d2f [maven-release-plugin] prepare for next development iteration 2018-02-28 14:30:32 +01:00
Dominik Przybysz
181f9dbcd9 [maven-release-plugin] prepare release http-mock-server-2.6.0 2018-02-28 14:30:22 +01:00
Piotr Fus
0727ced422 Add https support 2018-02-26 17:48:11 +01:00
piotrekfus91
f8e0cc44f9 Handle leading slash (#1)
* Handle leading slash
2018-01-28 18:30:50 +01:00
Dominik Przybysz
6036bd2c3b [maven-release-plugin] prepare for next development iteration 2017-06-02 08:32:23 +02:00
Dominik Przybysz
dbc0410f52 [maven-release-plugin] prepare release http-mock-server-2.5.1 2017-06-02 08:32:17 +02:00
Dominik Przybysz
5a3af95f0b Close http exchange at the end of http handler 2017-06-02 08:30:47 +02:00
Dominik Przybysz
ab2b3f9481 [maven-release-plugin] prepare for next development iteration 2017-05-31 09:57:08 +02:00
Dominik Przybysz
4bda860609 [maven-release-plugin] prepare release http-mock-server-2.5.0 2017-05-31 09:57:01 +02:00
Dominik Przybysz
4fb0c9cfce Fix memory leak on closure compilation 2017-05-31 09:53:38 +02:00
Dominik Przybysz
2b57ba0806 Do not create xml or json when request does not look like xml or json 2017-05-30 12:54:36 +02:00
Dominik Przybysz
3fcfec451a [maven-release-plugin] prepare for next development iteration 2016-04-12 17:10:19 +02:00
Dominik Przybysz
fca39a8355 [maven-release-plugin] prepare release http-mock-server-2.4.0 2016-04-12 17:10:15 +02:00
Dominik Przybysz
3599300191 Add preserveHistory option for mock 2015-12-28 14:11:02 +01:00
Dominik Przybysz
7a13cb2ce5 Add profile with performance tests 2015-12-28 09:18:53 +01:00
Dominik Adam Przybysz
951a0f1b56 Add logger configuration
Change-Id: I8a7c4ac0e09eb114845547202aa27792741cf549
2015-12-28 09:18:53 +01:00
Dominik Adam Przybysz
bfc547721e Add logger configuration
Change-Id: I8a7c4ac0e09eb114845547202aa27792741cf549
2015-12-28 09:18:53 +01:00
Dominik Adam Przybysz
229f2d02d3 Add params
Change-Id: I30d8d140a0ac01db9a5fbc184616fff487852109
2015-12-28 09:18:53 +01:00
Dominik Adam Przybysz
159f0a4987 Small fixes
Change-Id: I985d91d6d8ac2ff88760f5011e75711ad7c68649
2015-12-28 09:18:53 +01:00
Dominik Przybysz
d6b9abfe05 Add maven compiler plugin 2015-12-28 09:18:53 +01:00
Dominik Adam Przybysz
f110ffe8a9 Add benchmark in separate project
Change-Id: Ib0d29c334cf46744e33fa609eedce52892740af7
2015-12-28 09:18:53 +01:00
Dominik Przybysz
23e2813e2b Add link to docker hub 2015-12-23 11:42:24 +01:00
Dominik Przybysz
ba820bfcf2 [maven-release-plugin] prepare for next development iteration 2015-12-23 11:15:28 +01:00
Dominik Przybysz
da9cfc0ffb [maven-release-plugin] prepare release http-mock-server-2.3.0 2015-12-23 11:15:24 +01:00
Dominik Przybysz
44f44ee392 Add mocks configuration dump and restore 2015-12-23 11:14:49 +01:00
Dominik Przybysz
c02e93edc3 [maven-release-plugin] prepare for next development iteration 2015-12-22 15:26:02 +01:00
Dominik Przybysz
d272f70141 [maven-release-plugin] prepare release http-mock-server-2.2.0 2015-12-22 15:25:58 +01:00
Dominik Przybysz
493bb8fd91 Add imports support in closures 2015-12-22 15:24:06 +01:00
Dominik Adam Przybysz
261126d58b Add info about docker to README
Change-Id: I93b5a78cdce42c17a97423d65fb7ca05ecf19ced
2015-10-24 16:47:45 +02:00
Dominik Adam Przybysz
54739cff85 Add info about remote respoitory to README
Change-Id: Ia316e81b64c361620031f7bbc0031e81aad05e7b
2015-10-24 16:07:39 +02:00
Dominik Adam Przybysz
2b4ca48d79 [maven-release-plugin] prepare for next development iteration
Change-Id: I78090933a7aae4f9a53e3b688407dd2f7f7e988b
2015-10-24 15:49:31 +02:00
Dominik Adam Przybysz
631a651510 [maven-release-plugin] prepare release http-mock-server-2.1.1
Change-Id: Ib3b2ee456709c9c9ba1dab867f0db2b83c0e05c0
2015-10-24 15:49:25 +02:00
Dominik Adam Przybysz
e17e20fadb Change parent pom and repository
Change-Id: I7527e7f0da8fe0c0b4809074008ec97b93894992
2015-10-24 15:47:13 +02:00
Dominik Adam Przybysz
e104672d83 [maven-release-plugin] prepare for next development iteration
Change-Id: I227f5f5d3528f06c34333f582063d85da60b2335
2015-10-24 15:29:20 +02:00
Dominik Adam Przybysz
6b90bbdc81 [maven-release-plugin] prepare release http-mock-server-2.1.0
Change-Id: I4a700aa706a0de0f358635c30248778f8a6521ad
2015-10-24 15:29:14 +02:00
Dominik Adam Przybysz
f4dde98f17 Fix connection in scm
Change-Id: If051253e6ffc95ed789b2e173a7bb78d38160ce9
2015-10-24 15:28:13 +02:00
Dominik Adam Przybysz
70c0f678ef Let all modules release without asking
Change-Id: Ide781016b656ddbf392ae75bb8de7f45bd7e91b8
2015-10-24 15:09:54 +02:00
Dominik Adam Przybysz
1a5e0e5e17 Change scm in pom
Change-Id: I22f0eed1f3e23d2ffd5de32377d08dd04def2778
2015-10-24 15:07:00 +02:00
Dominik Adam Przybysz
3e695fcb78 Create full docker with static name
Change-Id: Id376cf70702371a7cd6496dab9ce85b7fdc643a3
2015-10-24 15:01:44 +02:00
Dominik Adam Przybysz
faf8f8006a Describe schema validation parameter
Change-Id: I45e2a2d5af6317c626d797613028693635e3c936
2015-10-24 14:16:26 +02:00
Dominik Adam Przybysz
9a14f9bfab Add request validation against xsd
Change-Id: Iadf618e50d01dd92bf9eed5b8ed98138b56c20c9
2015-10-24 13:46:52 +02:00
Dominik Przybysz
aabc9d75f2 Update version in Dockerfile 2015-09-19 20:54:52 +02:00
Dominik Przybysz
0f45ccd82a [maven-release-plugin] prepare for next development iteration 2015-09-19 20:27:29 +02:00
Dominik Przybysz
784e8ea8f0 [maven-release-plugin] prepare release http-mock-server-2.0.0 2015-09-19 20:27:28 +02:00
Dominik Adam Przybysz
49f211eef7 Fix after rebase
Change-Id: Ia01b2a6122b7fd778a90b8383d503f65c65e80ea
2015-09-19 20:19:44 +02:00
Dominik Adam Przybysz
b5a14d463e Mockserver works only on Java 8
Change-Id: I47e5ad027399cfeb5874837b3e206e052a4922da
2015-09-19 20:17:25 +02:00
Dominik Adam Przybysz
18e75f3352 Update readme of jaxb version
Change-Id: I09479e17c8e7c1bbc5be881c8526de63c681f4e4
2015-09-19 20:17:25 +02:00
Dominik Adam Przybysz
b75a6d84db Add docker compose configuration
Change-Id: I391482d93b0274a8b9b6500da94d6406d1341137
2015-09-19 20:17:25 +02:00
Dominik Adam Przybysz
80dbbc034f Add xsd and docker
Change-Id: Ifad2e3a8678a487e6f13114f910e19bce4d80964
2015-09-19 20:17:24 +02:00
Dominik Adam Przybysz
d71c191f0f Fix wrappers names in mock event reports
Change-Id: Iecbdbe6310358701f3ce22a97ce453adeefbb40e
2015-09-19 20:17:24 +02:00
Dominik Adam Przybysz
4e70d3b8bd Some smal improvements
Change-Id: I3559572293359bf3fb1c4e4e6892e19d4373ba63
2015-09-19 20:17:24 +02:00
Dominik Adam Przybysz
ed217704f4 Some changes in performance tests
Change-Id: Ieeb2ec1bc3c771cfb0e511b400ab4e6b7666b9c0
2015-09-19 20:17:24 +02:00
Dominik Adam Przybysz
82434f46a3 Make api with jaxb
Change-Id: Ic0ac5ce212fac17583699868709b67a701231755
2015-09-19 20:17:24 +02:00
Dominik Adam Przybysz
5545b67ebd Do not allow for System exit
Change-Id: I268b12dfab2e6021ad057a4dc9716a01efbe375d
2015-09-19 20:14:09 +02:00
Dominik Adam Przybysz
f03618873e Fix Readme
Change-Id: I719fd8f68b5f3435ff2a68ae6060cecaea09d51c
2015-08-21 16:40:32 +02:00
Dominik Przybysz
b105b78815 [maven-release-plugin] prepare for next development iteration 2015-08-21 08:11:06 +02:00
63 changed files with 3447 additions and 1112 deletions

5
.gitignore vendored Normal file
View file

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

18
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View file

@ -0,0 +1,18 @@
# 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

View file

@ -1,6 +0,0 @@
language: groovy
jdk:
- oraclejdk7
- oraclejdk8

59
.woodpecker.yaml Normal file
View file

@ -0,0 +1,59 @@
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

11
Dockerfile Normal file
View file

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

360
README.md
View file

@ -1,24 +1,109 @@
[![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
================
## Create server jar (in mockserver directory)
Http Mock Server allows to mock HTTP request using groovy closures.
Create server jar
-----------------
```
cd mockserver
mvn clean package assembly:single
```
## Start server on port (default 9999)
Start server
------------
### Native start
```
java -jar mockserver-<VERSION>-jar-with-dependencies.jar [PORT]
java -jar mockserver-full.jar [PORT] [CONFIGURATION_FILE]
```
## Create mock on server via client
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
Docker and docker-compose is needed.
```
./buildImage.sh
docker-compose up -d
```
### Docker repoository
Currently unavailable
Create mock on server
---------------------
### Via client
```java
RemoteMockServer remoteMockServer = new RemoteMockServer('localhost', <PORT>)
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: '...',
path: '...',
port: ...,
@ -27,15 +112,27 @@ remoteMockServer.addMock(new AddMockRequestData(
soap: ...,
statusCode: ...,
method: ...,
responseHeaders: ...
responseHeaders: ...,
schema: ...,
maxUses: ...,
cyclic: ...,
https: new Https(
keystorePath: '/tmp/keystore.jks',
keystorePassword: 'keystorePass',
keyPassword: 'keyPass',
truststorePath: '/tmp/truststore.jks',
truststorePassword: 'truststorePass',
requireClientAuth: true
)
))
```
or via sending POST request to localhost:<PORT>/serverControl
### Via HTTP
Send POST request to localhost:<PORT>/serverControl
```xml
<addMock>
<addMock xmlns="http://ztsh.eu/mockserver/api/request">
<name>...</name>
<path>...</path>
<port>...</port>
@ -45,53 +142,90 @@ or via sending POST request to localhost:<PORT>/serverControl
<statusCode>...</statusCode>
<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>
```
* 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 { _ -> [:] }
### 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.
### 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/>
<mockAdded xmlns="http://ztsh.eu/mockserver/api/response"/>
```
Response with error message if failure:
```xml
<exceptionOccured>...</exceptionOccured>
<exceptionOccured xmlns="http://ztsh.eu/mockserver/api/response">...</exceptionOccured>
```
## Mock could be peeked to get get report of its invocations.
Via client:
Peek mock
---------
Mock could be peeked to get get report of its invocations.
### Via client
```java
List<MockEvent> mockEvents = remoteMockServer.peekMock('...')
```
Via sending POST request to localhost:<PORT>/serverControl
### Via HTTP
Send POST request to localhost:<PORT>/serverControl
```xml
<peekMock>
<peekMock xmlns="http://ztsh.eu/mockserver/api/request">
<name>...</name>
</peekMock>
```
@ -99,105 +233,119 @@ Via sending POST request to localhost:<PORT>/serverControl
Response if success:
```xml
<mockPeeked>
<mockPeeked xmlns="http://ztsh.eu/mockserver/api/response">
<mockEvent>
<request>
<text>...</text>
<headers>
<param name='...'>...</param>
<header name="...">...</header>
...
</headers>
<query>
<param name='...'>...</param>
<queryParams>
<queryParam name="...">...</queryParam>
...
</query>
</queryParams>
<path>
<elem>...</elem>
<pathPart>...</pathPart>
...
</path>
</request>
<response>
<statusCode>...</statusCode>
<text>...</text>
<headers>
<param name='...'>...</param>
<header name="...">...</header>
...
</headers>
<statusCode>...</statusCode>
</response>
</mockEvent>
...
</mockPeeked>
```
Response with error message if failure:
```xml
<exceptionOccured>...</exceptionOccured>
<exceptionOccured xmlns="http://ztsh.eu/mockserver/api/response">...</exceptionOccured>
```
## When mock was used it could be unregistered by name. It also returns report of mock invocations.
Via client:
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.
### Via client
```java
List<MockEvent> mockEvents = remoteMockServer.removeMock('...')
List<MockEvent> mockEvents = remoteMockServer.removeMock('...', ...)
```
Via sending POST request to localhost:<PORT>/serverControl
### Via HTTP
Send POST request to localhost:<PORT>/serverControl
```xml
<removeMock>
<removeMock xmlns="http://ztsh.eu/mockserver/api/request">
<name>...</name>
<skipReport>...</skipReport>
</removeMock>
```
Response if success:
Response if success (and skipReport not given or equal false):
```xml
<mockRemoved>
<mockRemoved xmlns="http://ztsh.eu/mockserver/api/response">
<mockEvent>
<request>
<text>...</text>
<headers>
<param name='...'>...</param>
<header name="...">...</header>
...
</headers>
<query>
<param name='...'>...</param>
<queryParams>
<queryParam name="...">...</queryParam>
...
</query>
</queryParams>
<path>
<elem>...</elem>
<pathPart>...</pathPart>
...
</path>
</request>
<response>
<statusCode>...</statusCode>
<text>...</text>
<headers>
<param name='...'>...</param>
<header name="...">...</header>
...
</headers>
<statusCode>...</statusCode>
</response>
</mockEvent>
...
</mockRemoved>
```
If skipReport is set to true then response will be:
```xml
<mockRemoved xmlns="http://ztsh.eu/mockserver/api/response"/>
```
Response with error message if failure:
```xml
<exceptionOccured>...</exceptionOccured>
<exceptionOccured xmlns="http://ztsh.eu/mockserver/api/response">...</exceptionOccured>
```
List mocks definitions
----------------------
## List of current registered mocks could be retrieved:
Via client:
### Via client
```java
List<RegisteredMock> mocks = remoteMockServer.listMocks()
```
or via sending GET request to localhost:<PORT>/serverControl
### Via HTTP
Send GET request to localhost:<PORT>/serverControl
Response:
@ -207,7 +355,97 @@ Response:
<name>...</name>
<path>...</path>
<port>...</port>
<predicate>...</predicate>
<response>...</response>
<responseHeaders>...</responseHeaders>
<soap>...</soap>
<method>...</method>
<statusCode>...</statusCode>
<imports alias="..." fullClassName="..."/>
</mock>
...
</mocks>
```
Get mocks configuration
-----------------------
### 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`.
Just add repository to maven pom:
```xml
<project>
...
<repositories>
...
<repository>
<id>touk</id>
<url>https://philanthropist.ztsh.eu/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`.

5
buildImage.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
mvn -f mockserver/pom.xml clean package assembly:single
docker build -t mockserver .

6
docker-compose.yml Normal file
View file

@ -0,0 +1,6 @@
mocks:
image: mockserver
ports:
- "9999:9999"
volumes:
- /tmp:/externalSchema

42
mockserver-api/pom.xml Normal file
View file

@ -0,0 +1,42 @@
<?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-api</artifactId>
<dependencies>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<id>xjc</id>
<phase>process-resources</phase>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,9 @@
<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>

View file

@ -0,0 +1,34 @@
<?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:simpleType name="method">
<xs:restriction base="xs:string">
<xs:enumeration value="POST"/>
<xs:enumeration value="GET"/>
<xs:enumeration value="DELETE"/>
<xs:enumeration value="PUT"/>
<xs:enumeration value="TRACE"/>
<xs:enumeration value="HEAD"/>
<xs:enumeration value="OPTIONS"/>
<xs:enumeration value="PATCH"/>
<xs:enumeration value="ANY_METHOD" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="importAlias">
<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

@ -0,0 +1,57 @@
<?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:import namespace="http://ztsh.eu/mockserver/api/common" schemaLocation="common.xsd"/>
<xs:element name="addMock" type="tns:AddMock"/>
<xs:element name="peekMock" type="tns:PeekMock"/>
<xs:element name="removeMock" type="tns:RemoveMock"/>
<xs:complexType name="AddMock">
<xs:complexContent>
<xs:extension base="tns:mockServerRequest">
<xs:sequence>
<xs:element name="path" type="xs:string"/>
<xs:element name="port" type="xs:int"/>
<xs:element name="predicate" type="xs:string" minOccurs="0"/>
<xs:element name="response" type="xs:string" minOccurs="0"/>
<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>
</xs:complexType>
<xs:complexType name="mockServerRequest" abstract="true">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="PeekMock">
<xs:complexContent>
<xs:extension base="tns:mockServerRequest"/>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="RemoveMock">
<xs:complexContent>
<xs:extension base="tns:mockServerRequest">
<xs:sequence>
<xs:element name="skipReport" type="xs:boolean" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View file

@ -0,0 +1,137 @@
<?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:import namespace="http://ztsh.eu/mockserver/api/common" schemaLocation="common.xsd"/>
<xs:element name="exceptionOccured" type="tns:exceptionOccured"/>
<xs:element name="mockAdded" type="tns:mockAdded"/>
<xs:element name="mockPeeked" type="tns:mockPeeked"/>
<xs:element name="mockRemoved" type="tns:mockRemoved"/>
<xs:element name="mocks" type="tns:mockListing"/>
<xs:complexType name="exceptionOccured">
<xs:simpleContent>
<xs:extension base="xs:string"/>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="mockAdded">
<xs:complexContent>
<xs:extension base="tns:mockServerResponse">
<xs:sequence/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="mockServerResponse" abstract="true">
<xs:sequence/>
</xs:complexType>
<xs:complexType name="mockEventReport">
<xs:sequence>
<xs:element name="request" type="tns:mockRequestReport"/>
<xs:element name="response" type="tns:mockResponseReport"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="mockRequestReport">
<xs:sequence>
<xs:element name="text" type="xs:string" minOccurs="0"/>
<xs:element name="headers" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="header" type="tns:parameter" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="queryParams" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="queryParam" type="tns:parameter" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="path" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="pathPart" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="parameter">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="mockResponseReport">
<xs:sequence>
<xs:element name="statusCode" type="xs:int"/>
<xs:element name="text" type="xs:string" minOccurs="0"/>
<xs:element name="headers" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="header" type="tns:parameter" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="mockListing">
<xs:complexContent>
<xs:extension base="tns:mockServerResponse">
<xs:sequence>
<xs:element name="mock" type="tns:mockReport" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="mockReport">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="path" type="xs:string"/>
<xs:element name="port" type="xs:int"/>
<xs:element name="predicate" type="xs:string"/>
<xs:element name="response" type="xs:string"/>
<xs:element name="responseHeaders" type="xs:string"/>
<xs:element name="soap" type="xs:boolean"/>
<xs:element name="method" type="common:method"/>
<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>
<xs:complexType name="mockPeeked">
<xs:complexContent>
<xs:extension base="tns:mockServerResponse">
<xs:sequence>
<xs:element name="mockEvent" type="tns:mockEventReport" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="mockRemoved">
<xs:complexContent>
<xs:extension base="tns:mockServerResponse">
<xs:sequence>
<xs:element name="mockEvent" type="tns:mockEventReport" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View file

@ -1,22 +1,44 @@
<?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">
<parent>
<artifactId>http-mock-server</artifactId>
<groupId>pl.touk.mockserver</groupId>
<version>1.1.0</version>
</parent>
<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-client</artifactId>
<build>
<defaultGoal>clean install</defaultGoal>
</build>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-api</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>
@ -26,4 +48,14 @@
<artifactId>commons-lang3</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 pl.touk.mockserver.client
package eu.ztsh.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked

View file

@ -1,10 +1,9 @@
package pl.touk.mockserver.client
package eu.ztsh.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked
@CompileStatic
@TypeChecked
class PeekMockRequestData {
String name
class InvalidMockRequestSchema extends RuntimeException {
}

View file

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

View file

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

View file

@ -0,0 +1,85 @@
package eu.ztsh.mockserver.client
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpGet
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.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 jakarta.xml.bind.JAXBContext
class RemoteMockServer {
private final String address
private final CloseableHttpClient client = HttpClients.createDefault()
private static final JAXBContext requestContext = JAXBContext.newInstance(AddMock, PeekMock, RemoveMock)
RemoteMockServer(String host, int port) {
address = "http://$host:$port/serverControl"
}
void addMock(AddMock addMockData) {
HttpPost addMockPost = new HttpPost(address)
addMockPost.entity = buildAddMockRequest(addMockData)
CloseableHttpResponse response = client.execute(addMockPost)
Util.extractResponse(response)
}
List<MockEventReport> removeMock(String name, boolean skipReport = false) {
HttpPost removeMockPost = new HttpPost(address)
removeMockPost.entity = buildRemoveMockRequest(new RemoveMock(name: name, skipReport: skipReport))
CloseableHttpResponse response = client.execute(removeMockPost)
MockRemoved mockRemoved = Util.extractResponse(response) as MockRemoved
return mockRemoved.mockEvents ?: []
}
List<MockEventReport> peekMock(String name) {
HttpPost removeMockPost = new HttpPost(address)
removeMockPost.entity = buildPeekMockRequest(new PeekMock(name: name))
CloseableHttpResponse response = client.execute(removeMockPost)
MockPeeked mockPeeked = Util.extractResponse(response) as MockPeeked
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"))
}
private static String marshallRequest(MockServerRequest data) {
StringWriter sw = new StringWriter()
requestContext.createMarshaller().marshal(data, sw)
return sw.toString()
}
private static StringEntity buildPeekMockRequest(PeekMock peekMock) {
return new StringEntity(marshallRequest(peekMock), ContentType.create("text/xml", "UTF-8"))
}
private static StringEntity buildAddMockRequest(AddMock data) {
return new StringEntity(marshallRequest(data), ContentType.create("text/xml", "UTF-8"))
}
List<MockReport> listMocks() {
HttpGet get = new HttpGet(address)
CloseableHttpResponse response = client.execute(get)
Mocks mocks = Util.extractResponse(response) as Mocks
return mocks.mocks
}
}

View file

@ -0,0 +1,67 @@
package eu.ztsh.mockserver.client
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked
import groovy.xml.XmlSlurper
import groovy.xml.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 jakarta.xml.bind.JAXBContext
@CompileStatic
@TypeChecked
class Util {
private static
final JAXBContext responseContext = JAXBContext.newInstance(MockAdded.package.name, MockAdded.classLoader)
static GPathResult extractXmlResponse(CloseableHttpResponse response) {
return new XmlSlurper().parseText(extractStringResponse(response))
}
static String extractStringResponse(CloseableHttpResponse response) {
HttpEntity entity = response.entity
String responseString = EntityUtils.toString(entity, 'UTF-8')
EntityUtils.consumeQuietly(entity)
return responseString
}
static MockServerResponse extractResponse(CloseableHttpResponse response) {
String responseString = extractStringResponse(response)
if (response.statusLine.statusCode == 200) {
return responseContext.createUnmarshaller().unmarshal(new StringReader(responseString)) as MockServerResponse
}
ExceptionOccured exceptionOccured = responseContext.createUnmarshaller().unmarshal(new StringReader(responseString)) as ExceptionOccured
String message = exceptionOccured.value
if (message == 'mock already registered') {
throw new MockAlreadyExists()
}
if (message == 'mock not registered') {
throw new MockDoesNotExist()
}
if (message == 'mock request schema is invalid schema') {
throw new InvalidMockRequestSchema()
}
throw new InvalidMockDefinition(message)
}
static String soap(String request) {
return """<?xml version='1.0' encoding='UTF-8'?>
<soap-env:Envelope xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/' xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<soap-env:Body>$request</soap-env:Body>
</soap-env:Envelope>"""
}
static Object extractJsonResponse(CloseableHttpResponse response) {
return new JsonSlurper().parseText(extractStringResponse(response))
}
static void consumeResponse(CloseableHttpResponse response) {
EntityUtils.consumeQuietly(response.entity)
}
}

View file

@ -1,32 +0,0 @@
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked
import org.apache.commons.lang3.StringEscapeUtils
@CompileStatic
@TypeChecked
class AddMockRequestData {
String name
String path
Integer port
String predicate
String response
Boolean soap
Integer statusCode
Method method
String responseHeaders
void setPredicate(String predicate) {
this.predicate = StringEscapeUtils.escapeXml11(predicate)
}
void setResponse(String response) {
this.response = StringEscapeUtils.escapeXml11(response)
}
void setResponseHeaders(String responseHeaders) {
this.responseHeaders = StringEscapeUtils.escapeXml11(responseHeaders)
}
}

View file

@ -1,12 +0,0 @@
package pl.touk.mockserver.client
enum Method {
POST,
GET,
DELETE,
PUT,
TRACE,
HEAD,
OPTIONS,
PATCH
}

View file

@ -1,18 +0,0 @@
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.TypeChecked
@EqualsAndHashCode
@CompileStatic
@TypeChecked
class MockEvent {
final MockRequest request
final MockResponse response
MockEvent(MockRequest request, MockResponse response) {
this.request = request
this.response = response
}
}

View file

@ -1,22 +0,0 @@
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.TypeChecked
@CompileStatic
@TypeChecked
@EqualsAndHashCode
class MockRequest {
final String text
final Map<String, String> headers
final Map<String, String> query
final List<String> path
MockRequest(String text, Map<String, String> headers, Map<String, String> query, List<String> path) {
this.text = text
this.headers = headers
this.query = query
this.path = path
}
}

View file

@ -1,20 +0,0 @@
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.TypeChecked
@CompileStatic
@TypeChecked
@EqualsAndHashCode
class MockResponse {
final int statusCode
final String text
final Map<String, String> headers
MockResponse(int statusCode, String text, Map<String, String> headers) {
this.statusCode = statusCode
this.text = text
this.headers = headers
}
}

View file

@ -1,28 +0,0 @@
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
@CompileStatic
@TypeChecked
@EqualsAndHashCode
@ToString
class RegisteredMock {
final String name
final String path
final int port
final String predicate
final String response
final String responseHeaders
RegisteredMock(String name, String path, int port, String predicate, String response, String responseHeaders) {
this.name = name
this.path = path
this.port = port
this.predicate = predicate
this.response = response
this.responseHeaders = responseHeaders
}
}

View file

@ -1,119 +0,0 @@
package pl.touk.mockserver.client
import groovy.util.slurpersupport.GPathResult
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpGet
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
class RemoteMockServer {
private final String address
private final CloseableHttpClient client = HttpClients.createDefault()
RemoteMockServer(String host, int port) {
address = "http://$host:$port/serverControl"
}
void addMock(AddMockRequestData addMockRequestData) {
HttpPost addMockPost = new HttpPost(address)
addMockPost.entity = buildAddMockRequest(addMockRequestData)
CloseableHttpResponse response = client.execute(addMockPost)
GPathResult responseXml = Util.extractXmlResponse(response)
if (responseXml.name() != 'mockAdded') {
if (responseXml.text() == 'mock already registered') {
throw new MockAlreadyExists()
}
throw new InvalidMockDefinition(responseXml.text())
}
}
List<MockEvent> removeMock(String name, boolean skipReport = false) {
HttpPost removeMockPost = new HttpPost(address)
removeMockPost.entity = buildRemoveMockRequest(new RemoveMockRequestData(name: name, skipReport: skipReport))
CloseableHttpResponse response = client.execute(removeMockPost)
GPathResult responseXml = Util.extractXmlResponse(response)
if (responseXml.name() == 'mockRemoved') {
return responseXml.'mockEvent'.collect {
new MockEvent(mockRequestFromXml(it.request), mockResponseFromXml(it.response))
}
}
throw new MockDoesNotExist()
}
List<MockEvent> peekMock(String name) {
HttpPost removeMockPost = new HttpPost(address)
removeMockPost.entity = buildPeekMockRequest(new PeekMockRequestData(name: name))
CloseableHttpResponse response = client.execute(removeMockPost)
GPathResult responseXml = Util.extractXmlResponse(response)
if (responseXml.name() == 'mockPeeked') {
return responseXml.'mockEvent'.collect {
new MockEvent(mockRequestFromXml(it.request), mockResponseFromXml(it.response))
}
}
throw new MockDoesNotExist()
}
private static MockResponse mockResponseFromXml(GPathResult xml) {
return new MockResponse(xml.statusCode.text() as int, xml.text.text(), xml.headers.param.collectEntries {
[(it.@name.text()): it.text()]
})
}
private static MockRequest mockRequestFromXml(GPathResult xml) {
return new MockRequest(
xml.text.text(),
xml.headers.param.collectEntries { [(it.@name.text()): it.text()] },
xml.query.param.collectEntries { [(it.@name.text()): it.text()] },
xml.path.elem*.text()
)
}
private static StringEntity buildRemoveMockRequest(RemoveMockRequestData data) {
return new StringEntity("""\
<removeMock>
<name>${data.name}</name>
<skipReport>${data.skipReport}</skipReport>
</removeMock>
""", ContentType.create("text/xml", "UTF-8"))
}
private static StringEntity buildPeekMockRequest(PeekMockRequestData data) {
return new StringEntity("""\
<peekMock>
<name>${data.name}</name>
</peekMock>
""", ContentType.create("text/xml", "UTF-8"))
}
private static StringEntity buildAddMockRequest(AddMockRequestData data) {
return new StringEntity("""\
<addMock>
<name>${data.name}</name>
<path>${data.path}</path>
<port>${data.port}</port>
${data.predicate ? "<predicate>${data.predicate}</predicate>" : ''}
${data.response ? "<response>${data.response}</response>" : ''}
${data.soap != null ? "<soap>${data.soap}</soap>" : ''}
${data.statusCode ? "<statusCode>${data.statusCode}</statusCode>" : ''}
${data.method ? "<method>${data.method}</method>" : ''}
${data.responseHeaders ? "<responseHeaders>${data.responseHeaders}</responseHeaders>" : ''}
</addMock>
""", ContentType.create("text/xml", "UTF-8"))
}
List<RegisteredMock> listMocks() {
HttpGet get = new HttpGet(address)
CloseableHttpResponse response = client.execute(get)
GPathResult xml = Util.extractXmlResponse(response)
if (xml.name() == 'mocks') {
return xml.mock.collect {
new RegisteredMock(it.name.text(), it.path.text(), it.port.text() as int, it.predicate.text(), it.response.text(), it.responseHeaders.text())
}
}
return []
}
}

View file

@ -1,11 +0,0 @@
package pl.touk.mockserver.client
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked
@CompileStatic
@TypeChecked
class RemoveMockRequestData {
String name
boolean skipReport = false
}

View file

@ -1,38 +0,0 @@
package pl.touk.mockserver.client
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked
import groovy.util.slurpersupport.GPathResult
import org.apache.http.HttpEntity
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.util.EntityUtils
@CompileStatic
@TypeChecked
class Util {
static GPathResult extractXmlResponse(CloseableHttpResponse response) {
HttpEntity entity = response.entity
GPathResult xml = new XmlSlurper().parseText(EntityUtils.toString(entity, 'UTF-8'))
EntityUtils.consumeQuietly(entity)
return xml
}
static String soap(String request) {
return """<?xml version='1.0' encoding='UTF-8'?>
<soap-env:Envelope xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/' xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<soap-env:Body>$request</soap-env:Body>
</soap-env:Envelope>"""
}
static Object extractJsonResponse(CloseableHttpResponse response) {
HttpEntity entity = response.entity
Object json = new JsonSlurper().parseText(EntityUtils.toString(entity, 'UTF-8'))
EntityUtils.consumeQuietly(entity)
return json
}
static void consumeResponse(CloseableHttpResponse response) {
EntityUtils.consumeQuietly(response.entity)
}
}

View file

@ -1,49 +1,68 @@
<?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>
<parent>
<artifactId>http-mock-server</artifactId>
<groupId>pl.touk.mockserver</groupId>
<version>1.1.0</version>
<groupId>eu.ztsh.mockserver</groupId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mockserver-tests</artifactId>
<build>
<defaultGoal>clean install</defaultGoal>
</build>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver</artifactId>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-client</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-classic</artifactId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<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>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
</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

@ -0,0 +1,156 @@
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,25 +1,43 @@
package pl.touk.mockserver.tests
package eu.ztsh.mockserver.tests
import groovy.util.slurpersupport.GPathResult
import org.apache.http.client.methods.*
import groovy.xml.slurpersupport.GPathResult
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpDelete
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpHead
import org.apache.http.client.methods.HttpOptions
import org.apache.http.client.methods.HttpPatch
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.methods.HttpPut
import org.apache.http.client.methods.HttpTrace
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 org.apache.http.util.EntityUtils
import pl.touk.mockserver.client.*
import pl.touk.mockserver.server.HttpMockServer
import spock.lang.Shared
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 spock.lang.Specification
import spock.lang.Unroll
class MockServerIntegrationTest extends Specification {
RemoteMockServer remoteMockServer
@AutoCleanup('stop')
HttpMockServer httpMockServer
@Shared
CloseableHttpClient client = HttpClients.createDefault()
def setup() {
@ -27,13 +45,9 @@ 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 AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -52,9 +66,10 @@ 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 AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRestUtf',
path: 'testEndpoint',
port: 9999,
@ -76,7 +91,7 @@ class MockServerIntegrationTest extends Specification {
def "should add soap mock on endpoint"() {
expect:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint',
port: 9999,
@ -102,7 +117,7 @@ class MockServerIntegrationTest extends Specification {
then:
thrown(MockDoesNotExist)
expect:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint',
port: 9999,
@ -120,7 +135,7 @@ class MockServerIntegrationTest extends Specification {
def "should not add mock with existing name"() {
expect:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint',
port: 9999,
@ -129,7 +144,7 @@ class MockServerIntegrationTest extends Specification {
soap: true
))
when:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint2',
port: 9998,
@ -141,9 +156,23 @@ class MockServerIntegrationTest extends Specification {
thrown(MockAlreadyExists)
}
def "should not add mock when schema does not exist"() {
when:
remoteMockServer.addMock(new AddMock(
name: 'test',
path: 'testEndpoint2',
port: 9998,
response: '''{req -> "<goodResponse/>"}''',
soap: false,
schema: 'ble.xsd'
))
then:
thrown(InvalidMockRequestSchema)
}
def "should not add mock with empty name"() {
when:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: '',
path: 'testEndpoint2',
port: 9998,
@ -157,7 +186,7 @@ class MockServerIntegrationTest extends Specification {
def "should add mock after deleting old mock with the same name"() {
expect:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint',
port: 9999,
@ -168,7 +197,7 @@ class MockServerIntegrationTest extends Specification {
and:
remoteMockServer.removeMock('testSoap') == []
and:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint',
port: 9999,
@ -180,14 +209,14 @@ class MockServerIntegrationTest extends Specification {
def "should add simultaneously working post and rest mocks with the same predicate and endpoint nad port"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> "<goodResponseRest-${req.xml.name()}/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint',
port: 9999,
@ -212,17 +241,16 @@ class MockServerIntegrationTest extends Specification {
soapPostResponse.Body.'goodResponseSoap-request'.size() == 1
}
@Unroll
def "should dispatch rest mocks when second on #name"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest1',
path: 'test1',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request1'}''',
response: '''{req -> "<goodResponseRest1/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: secondPath,
port: secondPort,
@ -251,10 +279,9 @@ class MockServerIntegrationTest extends Specification {
9998 | 'test2' | 'another port and path'
}
@Unroll
def "should dispatch rest mock with response code"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest1',
path: 'test1',
port: 9999,
@ -276,7 +303,7 @@ class MockServerIntegrationTest extends Specification {
def "should return response code 404 and error body the same as request body when mocks does not apply"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest1',
path: 'test1',
port: 9999,
@ -295,7 +322,7 @@ class MockServerIntegrationTest extends Specification {
def "should inform that there was problem during adding mock - invalid port"() {
when:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint2',
port: -1,
@ -309,13 +336,13 @@ class MockServerIntegrationTest extends Specification {
def "should dispatch rest mock with get method"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
response: '''{_ -> "<defaultResponse/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'testEndpoint',
port: 9999,
@ -332,13 +359,13 @@ class MockServerIntegrationTest extends Specification {
def "should dispatch rest mock with trace method"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
response: '''{_ -> "<defaultResponse/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'testEndpoint',
port: 9999,
@ -355,13 +382,13 @@ class MockServerIntegrationTest extends Specification {
def "should dispatch rest mock with head method"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
response: '''{_ -> "<defaultResponse/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'testEndpoint',
port: 9999,
@ -377,13 +404,13 @@ class MockServerIntegrationTest extends Specification {
def "should dispatch rest mock with options method"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
response: '''{_ -> "<defaultResponse/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'testEndpoint',
port: 9999,
@ -399,13 +426,13 @@ class MockServerIntegrationTest extends Specification {
def "should dispatch rest mock with put method"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'test1',
port: 9999,
response: '''{_ -> "<defaultResponse/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'test1',
port: 9999,
@ -424,13 +451,13 @@ class MockServerIntegrationTest extends Specification {
def "should dispatch rest mock with delete method"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'test1',
port: 9999,
response: '''{_ -> "<defaultResponse/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'test1',
port: 9999,
@ -447,13 +474,13 @@ class MockServerIntegrationTest extends Specification {
def "should dispatch rest mock with patch method"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'test1',
port: 9999,
response: '''{_ -> "<defaultResponse/>"}'''
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'test1',
port: 9999,
@ -472,7 +499,7 @@ class MockServerIntegrationTest extends Specification {
def "should add mock that return headers"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -492,7 +519,7 @@ class MockServerIntegrationTest extends Specification {
def "should add mock that accepts only when certain request headers exists"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -521,7 +548,7 @@ class MockServerIntegrationTest extends Specification {
def "should add mock that accepts only when certain query params exists"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -545,7 +572,7 @@ class MockServerIntegrationTest extends Specification {
def "should add mock that accepts only when request has specific body"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -570,7 +597,7 @@ class MockServerIntegrationTest extends Specification {
def "should add mock which response json to json"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -593,9 +620,9 @@ class MockServerIntegrationTest extends Specification {
restPostResponse.name == 'goodResponse-1'
}
def "should get list mocks"() {
def "should get list of mocks"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'testEndpoint',
port: 9998,
@ -603,45 +630,58 @@ class MockServerIntegrationTest extends Specification {
response: '''{ req -> '<response/>' }''',
responseHeaders: '{ _ -> [a: "b"] }'
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest4',
path: 'testEndpoint',
port: 9999
port: 9999,
soap: true,
statusCode: 204,
method: Method.PUT
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest3',
path: 'testEndpoint2',
port: 9999
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest5',
path: 'testEndpoint',
port: 9999
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest6',
path: 'testEndpoint2',
port: 9999
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999
port: 9999,
schema: 'schema2.xsd',
imports: [new ImportAlias(alias: 'aaa', fullClassName: 'bbb')]
))
remoteMockServer.removeMock('testRest5')
expect:
remoteMockServer.listMocks() == [
new RegisteredMock('testRest', 'testEndpoint', 9999, '{ _ -> true }', '''{ _ -> '' }''', '{ _ -> [:] }'),
new RegisteredMock('testRest2', 'testEndpoint', 9998, '''{ req -> req.xml.name() == 'request1'}''', '''{ req -> '<response/>' }''', '{ _ -> [a: "b"] }'),
new RegisteredMock('testRest3', 'testEndpoint2', 9999, '{ _ -> true }', '''{ _ -> '' }''', '{ _ -> [:] }'),
new RegisteredMock('testRest4', 'testEndpoint', 9999, '{ _ -> true }', '''{ _ -> '' }''', '{ _ -> [:] }'),
new RegisteredMock('testRest6', 'testEndpoint2', 9999, '{ _ -> true }', '''{ _ -> '' }''', '{ _ -> [:] }')
]
when:
List<MockReport> mockReport = remoteMockServer.listMocks()
then:
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'])
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'
}
private static void assertMockReport(MockReport mockReport, Map<String, Object> props) {
props.each {
assert mockReport."${it.key}" == it.value
}
}
def "should add mock accepts path certain path params"() {
given:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -664,7 +704,7 @@ class MockServerIntegrationTest extends Specification {
def "should get mock report when deleting mock"() {
expect:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -674,7 +714,7 @@ class MockServerIntegrationTest extends Specification {
responseHeaders: '''{req -> ['aaa':'14']}''',
soap: false
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'testEndpoint',
port: 9999,
@ -706,40 +746,40 @@ class MockServerIntegrationTest extends Specification {
GPathResult restPostResponse3 = Util.extractXmlResponse(response3)
restPostResponse3.name() == 'goodResponseRest'
when:
List<MockEvent> mockEvents1 = remoteMockServer.removeMock('testRest')
List<MockEventReport> mockEvents1 = remoteMockServer.removeMock('testRest')
then:
mockEvents1.size() == 2
mockEvents1[0].request.text == '<request/>'
!mockEvents1[0].request.headers?.keySet()?.empty
mockEvents1[0].request.query == [:]
mockEvents1[0].request.path == ['testEndpoint']
!mockEvents1[0].response.headers?.keySet()?.empty
!mockEvents1[0].request.headers?.headers?.empty
mockEvents1[0].request.queryParams.queryParams == []
mockEvents1[0].request.path.pathParts == ['testEndpoint']
!mockEvents1[0].response.headers?.headers?.empty
mockEvents1[0].response.text == '<goodResponseRest-request/>'
mockEvents1[0].response.statusCode == 201
mockEvents1[1].request.text == '<request15/>'
!mockEvents1[1].request.headers?.keySet()?.empty
mockEvents1[1].request.query == [:]
mockEvents1[1].request.path == ['testEndpoint', 'hello']
!mockEvents1[1].response.headers?.keySet()?.empty
!mockEvents1[1].request.headers?.headers?.empty
mockEvents1[1].request.queryParams.queryParams == []
mockEvents1[1].request.path.pathParts == ['testEndpoint', 'hello']
!mockEvents1[1].response.headers?.headers?.empty
mockEvents1[1].response.text == '<goodResponseRest-request15/>'
mockEvents1[1].response.statusCode == 201
when:
List<MockEvent> mockEvents2 = remoteMockServer.removeMock('testRest2')
List<MockEventReport> mockEvents2 = remoteMockServer.removeMock('testRest2')
then:
mockEvents2.size() == 1
mockEvents2[0].request.text == '<reqXYZ/>'
!mockEvents2[0].request.headers?.keySet()?.empty
mockEvents2[0].request.query == [id: '123']
mockEvents2[0].request.path == ['testEndpoint']
mockEvents2[0].response.headers.aaa == '15'
!mockEvents2[0].request.headers?.headers?.empty
mockEvents2[0].request.queryParams.queryParams.find { it.name == 'id' }?.value == '123'
mockEvents2[0].request.path.pathParts == ['testEndpoint']
mockEvents2[0].response.headers.headers.find { it.name == 'aaa' }?.value == '15'
mockEvents2[0].response.text == '<goodResponseRest/>'
mockEvents2[0].response.statusCode == 202
}
def "should get mock report when peeking mock"() {
expect:
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -749,7 +789,7 @@ class MockServerIntegrationTest extends Specification {
responseHeaders: '''{req -> ['aaa':'14']}''',
soap: false
))
remoteMockServer.addMock(new AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest2',
path: 'testEndpoint',
port: 9999,
@ -781,41 +821,40 @@ class MockServerIntegrationTest extends Specification {
GPathResult restPostResponse3 = Util.extractXmlResponse(response3)
restPostResponse3.name() == 'goodResponseRest'
when:
List<MockEvent> mockEvents1 = remoteMockServer.peekMock('testRest')
List<MockEventReport> mockEvents1 = remoteMockServer.peekMock('testRest')
then:
mockEvents1.size() == 2
mockEvents1[0].request.text == '<request/>'
!mockEvents1[0].request.headers?.keySet()?.empty
mockEvents1[0].request.query == [:]
mockEvents1[0].request.path == ['testEndpoint']
!mockEvents1[0].response.headers?.keySet()?.empty
!mockEvents1[0].request.headers?.headers?.empty
mockEvents1[0].request.queryParams.queryParams == []
mockEvents1[0].request.path.pathParts == ['testEndpoint']
!mockEvents1[0].response.headers?.headers?.empty
mockEvents1[0].response.text == '<goodResponseRest-request/>'
mockEvents1[0].response.statusCode == 201
mockEvents1[1].request.text == '<request15/>'
!mockEvents1[1].request.headers?.keySet()?.empty
mockEvents1[1].request.query == [:]
mockEvents1[1].request.path == ['testEndpoint', 'hello']
!mockEvents1[1].response.headers?.keySet()?.empty
!mockEvents1[1].request.headers?.headers?.empty
mockEvents1[1].request.queryParams.queryParams == []
mockEvents1[1].request.path.pathParts == ['testEndpoint', 'hello']
!mockEvents1[1].response.headers?.headers?.empty
mockEvents1[1].response.text == '<goodResponseRest-request15/>'
mockEvents1[1].response.statusCode == 201
when:
List<MockEvent> mockEvents2 = remoteMockServer.peekMock('testRest2')
List<MockEventReport> mockEvents2 = remoteMockServer.peekMock('testRest2')
then:
mockEvents2.size() == 1
mockEvents2[0].request.text == '<reqXYZ/>'
!mockEvents2[0].request.headers?.keySet()?.empty
mockEvents2[0].request.query == [id: '123']
mockEvents2[0].request.path == ['testEndpoint']
mockEvents2[0].response.headers.aaa == '15'
!mockEvents2[0].request.headers?.headers?.empty
mockEvents2[0].request.queryParams.queryParams.find { it.name == 'id' }?.value == '123'
mockEvents2[0].request.path.pathParts == ['testEndpoint']
mockEvents2[0].response.headers.headers.find { it.name == 'aaa' }?.value == '15'
mockEvents2[0].response.text == '<goodResponseRest/>'
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 AddMockRequestData(
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
@ -839,4 +878,306 @@ class MockServerIntegrationTest extends Specification {
false | 1
true | 0
}
def "should reject mock when it has System.exit in closure"() {
when:
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
predicate: predicate,
response: '''{req -> "<goodResponseRest-${req.xml.name()}/>"}''',
soap: false
))
then:
thrown(InvalidMockDefinition)
expect:
remoteMockServer.listMocks() == []
where:
predicate << [
'''{req -> System.exit(-1); req.xml.name() == 'request'}''',
'''{req -> System .exit(-1); req.xml.name() == 'request'}''',
'''{req -> System
.exit(-1); req.xml.name() == 'request'}''',
'''{req -> System. exit(-1); req.xml.name() == 'request'}''',
'''{req -> System.exit (-1); req.xml.name() == 'request'}'''
]
}
def "should validate request against multiple schema files"() {
expect:
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
schema: 'schema1.xsd',
response: '''{req -> '<goodResponseRest/>'}''',
soap: false
))
when:
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity('<request xmlns="http://mockserver/test1"><id>15</id><value>unknown</value></request>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:
response.statusLine.statusCode == 400
Util.extractStringResponse(response).contains('''Value 'unknown' is not facet-valid with respect to enumeration '[test, prod, preprod]'.''')
when:
HttpPost restPost2 = new HttpPost('http://localhost:9999/testEndpoint')
restPost2.entity = new StringEntity('<request xmlns="http://mockserver/test1"><id>15</id><value>test</value></request>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response2 = client.execute(restPost2)
then:
Util.consumeResponse(response2)
response2.statusLine.statusCode == 200
expect:
remoteMockServer.removeMock('testRest')?.size() == 2
}
def "should validate soap request"() {
expect:
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint',
port: 9999,
schema: 'schema1.xsd',
response: '''{req -> '<goodResponse/>'}''',
soap: true
))
when:
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity(Util.soap('<request xmlns="http://mockserver/test1"><id>15</id><value>unknown</value></request>'), ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:
response.statusLine.statusCode == 400
Util.extractStringResponse(response).contains('''Value 'unknown' is not facet-valid with respect to enumeration '[test, prod, preprod]'.''')
when:
HttpPost restPost2 = new HttpPost('http://localhost:9999/testEndpoint')
restPost2.entity = new StringEntity(Util.soap('<request xmlns="http://mockserver/test1"><id>15</id><value>test</value></request>'), ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response2 = client.execute(restPost2)
then:
Util.consumeResponse(response2)
response2.statusLine.statusCode == 200
expect:
remoteMockServer.removeMock('testSoap')?.size() == 2
}
def "should validate soap request with namespace in envelope"() {
expect:
remoteMockServer.addMock(new AddMock(
name: 'testSoap',
path: 'testEndpoint',
port: 9999,
schema: 'schema1.xsd',
response: '''{req -> '<goodResponse/>'}''',
soap: true
))
when:
HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint')
restPost.entity = new StringEntity('''<soap-env:Envelope xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/'
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:req="http://mockserver/test1">
<soap-env:Body>
<req:request><req:id>15</req:id><req:value>unknown</req:value></req:request>
</soap-env:Body>
</soap-env:Envelope>''', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
then:
response.statusLine.statusCode == 400
Util.extractStringResponse(response).contains('''Value 'unknown' is not facet-valid with respect to enumeration '[test, prod, preprod]'.''')
when:
HttpPost restPost2 = new HttpPost('http://localhost:9999/testEndpoint')
restPost2.entity = new StringEntity('''<soap-env:Envelope xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/'
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:req="http://mockserver/test1">
<soap-env:Body>
<req:request><req:id>15</req:id><req:value>test</req:value></req:request>
</soap-env:Body>
</soap-env:Envelope>''', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response2 = client.execute(restPost2)
then:
Util.consumeResponse(response2)
response2.statusLine.statusCode == 200
expect:
remoteMockServer.removeMock('testSoap')?.size() == 2
}
def "should add mock with alias"() {
expect:
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
port: 9999,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> "<goodResponseRest-${AAA.XMLNS_ATTRIBUTE}/>"}''',
soap: false,
imports: [new ImportAlias(alias: 'AAA', fullClassName: 'javax.xml.XMLConstants')]
))
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-xmlns'
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,239 @@
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,53 +0,0 @@
package pl.touk.mockserver.tests
import groovy.util.slurpersupport.GPathResult
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.client.AddMockRequestData
import pl.touk.mockserver.client.RemoteMockServer
import pl.touk.mockserver.client.Util
import pl.touk.mockserver.server.HttpMockServer
import spock.lang.Specification
class ServerMockPT extends Specification {
def "should handle many request simultaneously"() {
given:
HttpMockServer httpMockServer = new HttpMockServer()
RemoteMockServer controlServerClient = new RemoteMockServer("localhost", 9999)
HttpClient client = HttpClients.createDefault()
int requestAmount = 1000
GPathResult[] responses = new GPathResult[requestAmount]
Thread[] threads = new Thread[requestAmount]
for (int i = 0; i < requestAmount; ++i) {
int current = i
threads[i] = new Thread({
int endpointNumber = current % 10
int port = 9000 + (current % 7)
controlServerClient.addMock(new AddMockRequestData(
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.extractXmlResponse(response)
assert controlServerClient.removeMock("testRest$current").size() == 1
})
}
when:
threads*.start()
Thread.sleep(60000)
then:
responses.eachWithIndex { res, i -> assert res.name() == "goodResponse$i" }
cleanup:
httpMockServer.stop()
}
}

Binary file not shown.

View file

@ -0,0 +1,19 @@
<xs:schema elementFormDefault="qualified"
version="1.0"
targetNamespace="http://mockserver/test1"
xmlns:tns="http://mockserver/test1"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:test2="http://mockserver/test2">
<xs:import namespace="http://mockserver/test2" schemaLocation="schema2.xsd"/>
<xs:element name="request" type="tns:Request"/>
<xs:complexType name="Request">
<xs:sequence>
<xs:element name="id" type="xs:int"/>
<xs:element name="value" type="test2:Value"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://mockserver/test2" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="Value">
<xs:restriction base="xs:string">
<xs:enumeration value="test"/>
<xs:enumeration value="prod"/>
<xs:enumeration value="preprod"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,49 +1,95 @@
<?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">
<parent>
<artifactId>http-mock-server</artifactId>
<groupId>pl.touk.mockserver</groupId>
<version>1.1.0</version>
</parent>
<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</artifactId>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>mockserver-api</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-classic</artifactId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</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>pl.touk.mockserver.server.Main</mainClass>
<mainClass>eu.ztsh.mockserver.server.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<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

@ -0,0 +1,112 @@
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

@ -0,0 +1,271 @@
package eu.ztsh.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 jakarta.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
@Slf4j
class HttpMockServer {
private final HttpServerWrapper httpServerWrapper
private final Map<Integer, HttpServerWrapper> 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)
initialConfiguration.values()?.each { ConfigObject co ->
addMock(co)
}
httpServerWrapper.createContext('/serverControl', {
HttpExchange ex ->
try {
if (ex.requestMethod == 'GET') {
if (ex.requestURI.path == '/serverControl/configuration') {
createResponse(ex, configuration.prettyPrint(), 200)
} else {
listMocks(ex)
}
} else if (ex.requestMethod == 'POST') {
MockServerRequest request = requestJaxbContext.createUnmarshaller().unmarshal(ex.requestBody) as MockServerRequest
if (request instanceof AddMock) {
addMock(request, ex)
} else if (request instanceof RemoveMock) {
removeMock(request, ex)
} else if (request instanceof PeekMock) {
peekMock(request, ex)
} else {
throw new RuntimeException('Unknown request')
}
} else {
throw new RuntimeException('Unknown request')
}
} catch (Exception e) {
createErrorResponse(ex, e)
}
})
}
void listMocks(HttpExchange ex) {
Mocks mockListing = new Mocks(
mocks: listMocks().collect {
new MockReport(
name: it.name,
path: it.path,
port: it.port,
predicate: it.predicateClosureText,
response: it.responseClosureText,
responseHeaders: it.responseHeadersClosureText,
soap: it.soap,
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
)
}
)
createResponse(ex, mockListing, 200)
}
Set<Mock> listMocks() {
return childServers.values().collect { it.mocks }.flatten() as TreeSet<Mock>
}
private void addMock(AddMock request, HttpExchange ex) {
String name = request.name
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)
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] } ?: [:]
mock.predicate = request.predicate
mock.response = request.response
mock.soap = request.soap
mock.statusCode = request.statusCode
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]
if (!child) {
child = new HttpServerWrapper(mockPort, executor, https)
childServers.put(mockPort, child)
}
return child
}
private void removeMock(RemoveMock request, HttpExchange ex) {
String name = request.name
boolean skipReport = request.skipReport ?: false
if (!(name in mockNames)) {
throw new RuntimeException('mock not registered')
}
log.info("Removing mock $name")
List<MockEvent> mockEvents = skipReport ? [] : childServers.values().collect {
it.removeMock(name)
}.flatten() as List<MockEvent>
mockNames.remove(name)
configuration.remove(name)
MockRemoved mockRemoved = new MockRemoved(
mockEvents: createMockEventReports(mockEvents)
)
createResponse(ex, mockRemoved, 200)
}
private static List<MockEventReport> createMockEventReports(List<MockEvent> mockEvents) {
return mockEvents.collect {
new MockEventReport(
request: new MockRequestReport(
text: it.request.text,
headers: new MockRequestReport.Headers(headers: it.request.headers.collect {
new Parameter(name: it.key, value: it.value)
}),
queryParams: new MockRequestReport.QueryParams(queryParams: it.request.query.collect {
new Parameter(name: it.key, value: it.value)
}),
path: new MockRequestReport.Path(pathParts: it.request.path)
),
response: new MockResponseReport(
statusCode: it.response.statusCode,
text: it.response.text,
headers: new MockResponseReport.Headers(headers: it.response.headers.collect {
new Parameter(name: it.key, value: it.value)
})
)
)
}
}
private void peekMock(PeekMock request, HttpExchange ex) {
String name = request.name
if (!(name in mockNames)) {
throw new RuntimeException('mock not registered')
}
log.trace("Peeking mock $name")
List<MockEvent> mockEvents = childServers.values().collect { it.peekMock(name) }.flatten() as List<MockEvent>
MockPeeked mockPeeked = new MockPeeked(
mockEvents: createMockEventReports(mockEvents)
)
createResponse(ex, mockPeeked, 200)
}
private static void createErrorResponse(HttpExchange ex, Exception e) {
log.warn('Exception occured', e)
createResponse(ex, new ExceptionOccured(value: e.message), 400)
}
void stop() {
childServers.values().each { it.stop() }
httpServerWrapper.stop()
}
}

View file

@ -0,0 +1,106 @@
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

@ -0,0 +1,28 @@
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

@ -0,0 +1,33 @@
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,199 @@
package eu.ztsh.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 javax.xml.XMLConstants
import javax.xml.transform.stream.StreamSource
import javax.xml.validation.SchemaFactory
import javax.xml.validation.Validator
import java.util.concurrent.CopyOnWriteArrayList
@PackageScope
@EqualsAndHashCode(excludes = ["counter"])
@Slf4j
class Mock implements Comparable<Mock> {
final String name
final String path
final int port
String predicateClosureText = '{ _ -> true }'
String responseClosureText = '''{ _ -> '' }'''
String responseHeadersClosureText = '{ _ -> [:] }'
Closure predicate = toClosure(predicateClosureText)
Closure response = toClosure(responseClosureText)
Closure responseHeaders = toClosure(responseHeadersClosureText)
boolean soap = false
int statusCode = 200
Method method = Method.POST
int counter = 0
final List<MockEvent> history = new CopyOnWriteArrayList<>()
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.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)
}
MockResponse apply(MockRequest request) {
log.debug("Mock $name invoked")
if (validator) {
try {
log.debug('Validating...')
if (soap) {
validator.validate(new StreamSource(new StringReader(request.textWithoutSoap)))
} else {
validator.validate(new StreamSource(new StringReader(request.text)))
}
} catch (Exception e) {
MockResponse response = new MockResponse(400, e.message, [:])
if(preserveHistory) {
history << new MockEvent(request, response)
}
return response
}
}
++counter
String responseText = response(request)
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)
}
return mockResponse
}
private static String wrapSoap(String request) {
"""<?xml version='1.0' encoding='UTF-8'?>
<soap-env:Envelope xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/' xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<soap-env:Body>${request}</soap-env:Body>
</soap-env:Envelope>"""
}
void setPredicate(String predicate) {
if (predicate) {
this.predicateClosureText = predicate
this.predicate = toClosure(predicate)
}
}
private Closure toClosure(String predicate) {
if (predicate ==~ /(?m).*System\s*\.\s*exit\s*\(.*/) {
throw new RuntimeException('System.exit is forbidden')
}
CompilerConfiguration compilerConfiguration = new CompilerConfiguration()
ImportCustomizer customizer = new ImportCustomizer()
imports.each {
customizer.addImport(it.key, it.value)
}
compilerConfiguration.addCompilationCustomizers(customizer)
GroovyShell sh = new GroovyShell(this.class.classLoader, compilerConfiguration);
Closure closure = sh.evaluate(predicate) as Closure
sh.resetLoadedClasses()
return closure
}
void setResponse(String response) {
if (response) {
this.responseClosureText = response
this.response = toClosure(response)
}
}
void setSoap(Boolean soap) {
this.soap = soap ?: false
}
void setStatusCode(String statusCode) {
if (statusCode) {
this.statusCode = Integer.valueOf(statusCode)
}
}
void setMethod(Method method) {
if (method) {
this.method = method
}
}
void setResponseHeaders(String responseHeaders) {
if (responseHeaders) {
this.responseHeadersClosureText = responseHeaders
this.responseHeaders = toClosure(responseHeaders)
}
}
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)
}
void setSchema(String schema) {
this.schema = schema
if (schema) {
try {
validator = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
.newSchema(this.class.getResource("/$schema"))
.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 pl.touk.mockserver.server
package eu.ztsh.mockserver.server
import groovy.transform.PackageScope

View file

@ -1,9 +1,11 @@
package pl.touk.mockserver.server
package eu.ztsh.mockserver.server
import com.sun.net.httpserver.Headers
import groovy.json.JsonSlurper
import groovy.transform.PackageScope
import groovy.util.slurpersupport.GPathResult
import groovy.xml.XmlSlurper
import groovy.xml.slurpersupport.GPathResult
import groovy.xml.XmlUtil
@PackageScope
class MockRequest {
@ -26,6 +28,9 @@ class MockRequest {
}
private static GPathResult inputToXml(String text) {
if (!text.startsWith('<')) {
return null
}
try {
return new XmlSlurper().parseText(text)
} catch (Exception _) {
@ -35,7 +40,7 @@ class MockRequest {
private static GPathResult inputToSoap(GPathResult xml) {
try {
if (xml.name() == 'Envelope' && xml.Body.size() > 0) {
if (xml != null && xml.name() == 'Envelope' && xml.Body.size() > 0) {
return getSoapBodyContent(xml)
} else {
return null
@ -46,10 +51,13 @@ class MockRequest {
}
private static GPathResult getSoapBodyContent(GPathResult xml) {
return xml.Body.'**'[1]
return xml.Body.'**'[1] as GPathResult
}
private static Object inputToJson(String text) {
if (!text.startsWith('[') && !text.startsWith('{')) {
return null
}
try {
return new JsonSlurper().parseText(text)
} catch (Exception _) {
@ -66,7 +74,10 @@ class MockRequest {
private static Map<String, String> headersToMap(Headers headers) {
return headers.collectEntries {
[it.key.toLowerCase(), it.value.join(',')]
}
} as Map<String, String>
}
String getTextWithoutSoap() {
return XmlUtil.serialize(soap)
}
}

View file

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

View file

@ -0,0 +1,32 @@
package eu.ztsh.mockserver.server
import com.sun.net.httpserver.HttpExchange
import eu.ztsh.mockserver.api.response.MockAdded
import jakarta.xml.bind.JAXBContext
class Util {
private static
final JAXBContext responseJaxbContext = JAXBContext.newInstance(MockAdded.package.name, MockAdded.classLoader)
static void createResponse(HttpExchange ex, Object response, int statusCode) {
String responseString = marshall(response)
createResponse(ex, responseString, statusCode)
}
static void createResponse(HttpExchange ex, String responseString, int statusCode) {
byte[] responseBytes = responseString ? responseString.getBytes('UTF-8') : new byte[0]
ex.sendResponseHeaders(statusCode, responseBytes.length ?: -1)
if (responseString) {
ex.responseBody << responseBytes
ex.responseBody.close()
}
}
private static String marshall(Object response) {
StringWriter sw = new StringWriter()
responseJaxbContext.createMarshaller().marshal(response, sw)
return sw.toString()
}
}

View file

@ -1,83 +0,0 @@
package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpExchange
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
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(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,191 +0,0 @@
package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpExchange
import groovy.util.logging.Slf4j
import groovy.util.slurpersupport.GPathResult
import groovy.xml.MarkupBuilder
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArraySet
import static pl.touk.mockserver.server.Util.createResponse
@Slf4j
class HttpMockServer {
private final HttpServerWraper httpServerWraper
private final List<HttpServerWraper> childServers = new CopyOnWriteArrayList<>()
private final Set<String> mockNames = new CopyOnWriteArraySet<>()
HttpMockServer(int port = 9999) {
httpServerWraper = new HttpServerWraper(port)
httpServerWraper.createContext('/serverControl', {
HttpExchange ex ->
try {
if (ex.requestMethod == 'GET') {
listMocks(ex)
} else if (ex.requestMethod == 'POST') {
GPathResult request = new XmlSlurper().parse(ex.requestBody)
if (request.name() == 'addMock') {
addMock(request, ex)
} else if (request.name() == 'removeMock') {
removeMock(request, ex)
} else if (request.name() == 'peekMock') {
peekMock(request, ex)
} else {
throw new RuntimeException('Unknown request')
}
} else {
throw new RuntimeException('Unknown request')
}
} catch (Exception e) {
createErrorResponse(ex, e)
}
})
}
void listMocks(HttpExchange ex) {
StringWriter sw = new StringWriter()
MarkupBuilder builder = new MarkupBuilder(sw)
builder.mocks {
listMocks().each {
Mock mock ->
builder.mock {
name mock.name
path mock.path
port mock.port
predicate mock.predicateClosureText
response mock.responseClosureText
responseHeaders mock.responseHeadersClosureText
}
}
}
createResponse(ex, sw.toString(), 200)
}
Set<Mock> listMocks() {
return childServers.collect { it.mocks }.flatten() as TreeSet
}
private void addMock(GPathResult request, HttpExchange ex) {
String name = request.name
if (name in mockNames) {
throw new RuntimeException('mock already registered')
}
Mock mock = mockFromRequest(request)
HttpServerWraper child = getOrCreateChildServer(mock.port)
child.addMock(mock)
mockNames << name
createResponse(ex, '<mockAdded/>', 200)
}
private static Mock mockFromRequest(GPathResult request) {
String name = request.name
String mockPath = request.path
int mockPort = Integer.valueOf(request.port as String)
Mock mock = new Mock(name, mockPath, mockPort)
mock.predicate = request.predicate
mock.response = request.response
mock.soap = request.soap
mock.statusCode = request.statusCode
mock.method = request.method
mock.responseHeaders = request.responseHeaders
return mock
}
private HttpServerWraper getOrCreateChildServer(int mockPort) {
HttpServerWraper child = childServers.find { it.port == mockPort }
if (!child) {
child = new HttpServerWraper(mockPort)
childServers << child
}
return child
}
private void removeMock(GPathResult request, HttpExchange ex) {
String name = request.name
boolean skipReport = Boolean.parseBoolean(request.skipReport?.toString() ?: 'false')
if (!(name in mockNames)) {
throw new RuntimeException('mock not registered')
}
log.info("Removing mock $name")
List<MockEvent> mockEvents = skipReport ? [] : childServers.collect { it.removeMock(name) }.flatten()
mockNames.remove(name)
createResponse(ex, createMockRemovedResponse(mockEvents), 200)
}
private void peekMock(GPathResult request, HttpExchange ex) {
String name = request.name
if (!(name in mockNames)) {
throw new RuntimeException('mock not registered')
}
log.trace("Peeking mock $name")
List<MockEvent> mockEvents = childServers.collect { it.peekMock(name) }.flatten()
createResponse(ex, createMockPeekedResponse(mockEvents), 200)
}
private static String createMockRemovedResponse(List<MockEvent> mockEvents) {
StringWriter sw = new StringWriter()
MarkupBuilder builder = new MarkupBuilder(sw)
builder.mockRemoved {
mockEventsToXml(mockEvents, builder)
}
return sw.toString()
}
private static String createMockPeekedResponse(List<MockEvent> mockEvents) {
StringWriter sw = new StringWriter()
MarkupBuilder builder = new MarkupBuilder(sw)
builder.mockPeeked {
mockEventsToXml(mockEvents, builder)
}
return sw.toString()
}
private static void mockEventsToXml(List<MockEvent> events, MarkupBuilder builder) {
events.each { MockEvent event ->
builder.mockEvent {
builder.request {
text event.request.text
headers {
event.request.headers.each {
builder.param(name: it.key, it.value)
}
}
query {
event.request.query.each {
builder.param(name: it.key, it.value)
}
}
path {
event.request.path.each {
builder.elem it
}
}
}
builder.response {
text event.response.text
headers {
event.response.headers.each {
builder.param(name: it.key, it.value)
}
}
statusCode event.response.statusCode
}
}
}
}
private static void createErrorResponse(HttpExchange ex, Exception e) {
StringWriter sw = new StringWriter()
MarkupBuilder builder = new MarkupBuilder(sw)
builder.exceptionOccured e.message
createResponse(ex, sw.toString(), 400)
}
void stop() {
childServers.each { it.stop() }
httpServerWraper.stop()
}
}

View file

@ -1,58 +0,0 @@
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.newCachedThreadPool()
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()
}
List<MockEvent> peekMock(String name) {
return executors.collect { it.peekMock(name) }.flatten()
}
List<Mock> getMocks() {
return executors.collect { it.mocks }.flatten()
}
}

View file

@ -1,20 +0,0 @@
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,107 +0,0 @@
package pl.touk.mockserver.server
import groovy.transform.EqualsAndHashCode
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import java.util.concurrent.CopyOnWriteArrayList
@PackageScope
@EqualsAndHashCode(excludes = ["counter"])
@Slf4j
class Mock implements Comparable<Mock> {
final String name
final String path
final int port
String predicateClosureText = '{ _ -> true }'
String responseClosureText = '''{ _ -> '' }'''
String responseHeadersClosureText = '{ _ -> [:] }'
Closure predicate = toClosure(predicateClosureText)
Closure response = toClosure(responseClosureText)
Closure responseHeaders =toClosure(responseHeadersClosureText)
boolean soap = false
int statusCode = 200
String method = 'POST'
int counter = 0
final List<MockEvent> history = new CopyOnWriteArrayList<>()
Mock(String name, String path, int port) {
if (!(name)) {
throw new RuntimeException("Mock name must be given")
}
this.name = name
this.path = path
this.port = port
}
boolean match(String method, MockRequest request) {
return this.method == method && predicate(request)
}
MockResponse apply(MockRequest request) {
log.debug("Mock $name invoked")
++counter
String responseText = response(request)
String response = soap ? wrapSoap(responseText) : responseText
Map<String, String> headers = responseHeaders(request)
MockResponse mockResponse = new MockResponse(statusCode, response, headers)
history << new MockEvent(request, mockResponse)
return mockResponse
}
private static String wrapSoap(String request) {
"""<?xml version='1.0' encoding='UTF-8'?>
<soap-env:Envelope xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/' xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<soap-env:Body>${request}</soap-env:Body>
</soap-env:Envelope>"""
}
void setPredicate(String predicate) {
if (predicate) {
this.predicateClosureText = predicate
this.predicate = toClosure(predicate)
}
}
private Closure toClosure(String predicate) {
GroovyShell sh = new GroovyShell(this.class.classLoader);
return sh.evaluate(predicate) as Closure
}
void setResponse(String response) {
if (response) {
this.responseClosureText = response
this.response = toClosure(response)
}
}
void setSoap(String soap) {
if (soap) {
this.soap = Boolean.valueOf(soap)
}
}
void setStatusCode(String statusCode) {
if (statusCode) {
this.statusCode = Integer.valueOf(statusCode)
}
}
void setMethod(String method) {
if (method) {
this.method = method
}
}
void setResponseHeaders(String responseHeaders) {
if (responseHeaders) {
this.responseHeadersClosureText = responseHeaders
this.responseHeaders = toClosure(responseHeaders)
}
}
@Override
int compareTo(Mock o) {
return name.compareTo(o.name)
}
}

View file

@ -1,14 +0,0 @@
package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpExchange
class Util {
static void createResponse(HttpExchange ex, String response, int statusCode) {
byte[] responseBytes = response ? response.getBytes('UTF-8') : new byte[0]
ex.sendResponseHeaders(statusCode, responseBytes.length ?: -1)
if (response) {
ex.responseBody << responseBytes
ex.responseBody.close()
}
}
}

308
mvnw vendored Executable file
View file

@ -0,0 +1,308 @@
#!/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 Normal file
View file

@ -0,0 +1,205 @@
@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%

70
performance-tests/pom.xml Normal file
View file

@ -0,0 +1,70 @@
<?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

@ -0,0 +1,82 @@
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

@ -0,0 +1,15 @@
<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>

143
pom.xml
View file

@ -1,53 +1,90 @@
<?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>pl.touk.mockserver</groupId>
<groupId>eu.ztsh.mockserver</groupId>
<artifactId>http-mock-server</artifactId>
<packaging>pom</packaging>
<version>1.1.0</version>
<version>3.0.0-SNAPSHOT</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>
<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.7</slf4j-api.version>
<logback-classic.version>1.0.13</logback-classic.version>
</properties>
<scm>
<connection>scm:git:ssh://gerrit.touk.pl:29418/integracja/http-mock-server</connection>
<developerConnection>scm:git:ssh://gerrit.touk.pl:29418/integracja/http-mock-server</developerConnection>
<tag>HEAD</tag>
</scm>
<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>
<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>
<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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<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>
<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>
@ -58,44 +95,66 @@
<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-classic.version}</version>
<version>${logback.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>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<defaultGoal>clean install</defaultGoal>
<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>1.4</version>
<version>${gmavenplus-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
<distributionManagement>
<repository>
<id>touk.nexus.release</id>
<name>TouK Virtual Repository</name>
<url>http://nexus.touk.pl/nexus/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>touk.nexus.snapshots</id>
<name>TouK Virtual Repository</name>
<url>http://nexus.touk.pl/nexus/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
</project>