diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8b71f67..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*.iml -target/ -.idea - -.mvn/wrapper/maven-wrapper.jar \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index f3283b0..0000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ad81e63 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: groovy + +jdk: + - oraclejdk7 + - oraclejdk8 + diff --git a/.woodpecker.yaml b/.woodpecker.yaml deleted file mode 100644 index 5921e36..0000000 --- a/.woodpecker.yaml +++ /dev/null @@ -1,59 +0,0 @@ -variables: - &maven_image maven:3.9.6-eclipse-temurin-11-alpine - -when: - evaluate: 'not (CI_COMMIT_MESSAGE contains "Release")' - -steps: - - name: build - image: *maven_image - commands: - - mvn -B clean install -DskipTests -Dmaven.test.skip - - name: test - image: *maven_image - commands: - - mvn -B -pl :mockserver-tests verify - - name: deploy to public - image: *maven_image - commands: - - mvn -B jar:jar deploy:deploy - secrets: [reposilite_user, reposilite_token] - when: - branch: [dev, master] - - name: deploy to releases - image: woodpeckerci/plugin-gitea-release - settings: - base-url: https://git.ztsh.eu - files: - - "mockserver-client/target/mockserver-client*.jar" - - "mockserver/target/mockserver-full.jar" - api_key: - from_secret: git_pat - when: - - event: tag - - name: tag docker image - image: woodpeckerci/plugin-docker-buildx - settings: - platforms: linux/amd64,linux/arm64/v8,linux/ppc64le,linux/s390x - repo: ztsheu/http-mock-server - registry: docker.io - tags: ${CI_COMMIT_TAG} - username: ztsheu - password: - from_secret: docker_pat - when: - - event: tag - - name: build docker image - image: woodpeckerci/plugin-docker-buildx - settings: - platforms: linux/amd64,linux/arm64/v8,linux/ppc64le,linux/s390x - repo: ztsheu/http-mock-server - registry: docker.io - tags: latest - username: ztsheu - password: - from_secret: docker_pat - when: - - event: tag - - event: push - branch: dev diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 3250eed..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -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 diff --git a/README.md b/README.md index cb67c96..e2ce7c8 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,24 @@ [![Build Status](https://img.shields.io/travis/TouK/http-mock-server/master.svg?style=flat)](https://travis-ci.org/TouK/http-mock-server) -HTTP MOCK SERVER -================ +# HTTP MOCK SERVER -Http Mock Server allows to mock HTTP request using groovy closures. - -Create server jar ------------------ +## Create server jar (in mockserver directory) ``` -cd mockserver mvn clean package assembly:single ``` -Start server ------------- - -### Native start +## Start server on port (default 9999) ``` -java -jar mockserver-full.jar [PORT] [CONFIGURATION_FILE] +java -jar mockserver--jar-with-dependencies.jar [PORT] ``` -Default port is 9999. - -If configuration file is passed then port must be defined. - -Configuration file is groovy configuration script e.g. : - -```groovy -testRest2 { - port=9998 - response='{ req -> \'\' }' - 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 +## Create mock on server via client ```java RemoteMockServer remoteMockServer = new RemoteMockServer('localhost', ) -remoteMockServer.addMock(new AddMock( +remoteMockServer.addMock(new AddMockRequestData( name: '...', path: '...', port: ..., @@ -112,27 +27,15 @@ remoteMockServer.addMock(new AddMock( soap: ..., statusCode: ..., method: ..., - responseHeaders: ..., - schema: ..., - maxUses: ..., - cyclic: ..., - https: new Https( - keystorePath: '/tmp/keystore.jks', - keystorePassword: 'keystorePass', - keyPassword: 'keyPass', - truststorePath: '/tmp/truststore.jks', - truststorePassword: 'truststorePass', - requireClientAuth: true - ) + responseHeaders: ... )) ``` + +or via sending POST request to localhost:/serverControl -### Via HTTP - -Send POST request to localhost:/serverControl ```xml - + ... ... ... @@ -142,210 +45,159 @@ Send POST request to localhost:/serverControl ... ... ... - ... - - ... - ... - - /tmp/keystore.jks - keystorePass - keyPass - /tmp/truststore.jks - truststorePass - true - ``` -### 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 +* 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 { _ -> [:] } 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 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 with not empty parts of request path Response if success: ```xml - + ``` Response with error message if failure: ```xml -... +... ``` -Peek mock ---------- - -Mock could be peeked to get get report of its invocations. - -### Via client +## Mock could be peeked to get get report of its invocations. +Via client: ```java List mockEvents = remoteMockServer.peekMock('...') ``` -### Via HTTP - -Send POST request to localhost:/serverControl +Via sending POST request to localhost:/serverControl ```xml - - ... + + ... ``` Response if success: ```xml - + ... -
...
+ ... ...
- - ... + + ... ... - + - ... + ... ...
- ... ... -
...
+ ... ...
+ ...
+ ...
``` Response with error message if failure: ```xml -... +... ``` -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 +## When mock was used it could be unregistered by name. It also returns report of mock invocations. +Via client: ```java -List mockEvents = remoteMockServer.removeMock('...', ...) +List mockEvents = remoteMockServer.removeMock('...') ``` -### Via HTTP - -Send POST request to localhost:/serverControl +Via sending POST request to localhost:/serverControl ```xml - + ... - ... ``` -Response if success (and skipReport not given or equal false): +Response if success: ```xml - + ... -
...
+ ... ...
- - ... + + ... ... - + - ... + ... ...
- ... ... -
...
+ ... ...
+ ...
+ ...
``` -If skipReport is set to true then response will be: - -```xml - -``` - Response with error message if failure: ```xml -... +... ``` -List mocks definitions ----------------------- -### Via client +## List of current registered mocks could be retrieved: +Via client: ```java List mocks = remoteMockServer.listMocks() ``` -### Via HTTP - -Send GET request to localhost:/serverControl +or via sending GET request to localhost:/serverControl Response: @@ -355,97 +207,7 @@ Response: ... ... ... - ... - ... - ... - ... - ... - ... - ... ``` - -Get mocks configuration ------------------------ - -### Via client - -```java -ConfigObject mocks = remoteMockServer.getConfiguration() -``` - -### Via HTTP - -Send GET request to localhost:/serverControl/configuration - -Response: - -```groovy -testRest2 { - port=9998 - response='{ req -> \'\' }' - 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 - - ... - - ... - - touk - https://philanthropist.ztsh.eu/nexus/content/repositories/releases - - ... - - ... - -``` - -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`. diff --git a/buildImage.sh b/buildImage.sh deleted file mode 100755 index 7916512..0000000 --- a/buildImage.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -mvn -f mockserver/pom.xml clean package assembly:single - -docker build -t mockserver . diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 6cb6ea7..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,6 +0,0 @@ -mocks: - image: mockserver - ports: - - "9999:9999" - volumes: - - /tmp:/externalSchema diff --git a/mockserver-api/pom.xml b/mockserver-api/pom.xml deleted file mode 100644 index de5029a..0000000 --- a/mockserver-api/pom.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - 4.0.0 - - - eu.ztsh.mockserver - http-mock-server - 3.0.0-SNAPSHOT - - - mockserver-api - - - - jakarta.xml.bind - jakarta.xml.bind-api - - - org.projectlombok - lombok - - - - - - - org.codehaus.mojo - jaxb2-maven-plugin - - - xjc - process-resources - - xjc - - - - - - - \ No newline at end of file diff --git a/mockserver-api/src/main/xjb/binding.xjb b/mockserver-api/src/main/xjb/binding.xjb deleted file mode 100644 index fdf2585..0000000 --- a/mockserver-api/src/main/xjb/binding.xjb +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/common.xsd b/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/common.xsd deleted file mode 100644 index 6dac2a9..0000000 --- a/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/common.xsd +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/request.xsd b/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/request.xsd deleted file mode 100644 index 891aebf..0000000 --- a/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/request.xsd +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/response.xsd b/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/response.xsd deleted file mode 100644 index 66d4566..0000000 --- a/mockserver-api/src/main/xsd/eu/ztsh/mockserver/api/response.xsd +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mockserver-client/pom.xml b/mockserver-client/pom.xml index cb7acfb..b5a1ba6 100644 --- a/mockserver-client/pom.xml +++ b/mockserver-client/pom.xml @@ -1,44 +1,22 @@ - - 4.0.0 - + - eu.ztsh.mockserver http-mock-server - 3.0.0-SNAPSHOT + pl.touk.mockserver + 1.1.0 + 4.0.0 mockserver-client + + clean install + - eu.ztsh.mockserver - mockserver-api + org.codehaus.groovy + groovy-all - - - org.apache.groovy - groovy - - - org.apache.groovy - groovy-json - - - org.apache.groovy - groovy-xml - - - - org.glassfish.jaxb - jaxb-core - - - org.glassfish.jaxb - jaxb-runtime - - org.apache.httpcomponents httpclient @@ -48,14 +26,4 @@ commons-lang3 - - - - - org.codehaus.gmavenplus - gmavenplus-plugin - - - - diff --git a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/RemoteMockServer.groovy b/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/RemoteMockServer.groovy deleted file mode 100644 index f594614..0000000 --- a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/RemoteMockServer.groovy +++ /dev/null @@ -1,85 +0,0 @@ -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 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 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 listMocks() { - HttpGet get = new HttpGet(address) - CloseableHttpResponse response = client.execute(get) - Mocks mocks = Util.extractResponse(response) as Mocks - return mocks.mocks - } -} diff --git a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/Util.groovy b/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/Util.groovy deleted file mode 100644 index e79e0c3..0000000 --- a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/Util.groovy +++ /dev/null @@ -1,67 +0,0 @@ -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 """ - - $request - """ - } - - static Object extractJsonResponse(CloseableHttpResponse response) { - return new JsonSlurper().parseText(extractStringResponse(response)) - } - - static void consumeResponse(CloseableHttpResponse response) { - EntityUtils.consumeQuietly(response.entity) - } - -} diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/AddMockRequestData.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/AddMockRequestData.groovy new file mode 100644 index 0000000..6da9a6e --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/AddMockRequestData.groovy @@ -0,0 +1,32 @@ +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) + } +} + diff --git a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/InvalidMockDefinition.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/InvalidMockDefinition.groovy similarity index 86% rename from mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/InvalidMockDefinition.groovy rename to mockserver-client/src/main/groovy/pl/touk/mockserver/client/InvalidMockDefinition.groovy index a39ef77..f95b584 100644 --- a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/InvalidMockDefinition.groovy +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/InvalidMockDefinition.groovy @@ -1,4 +1,4 @@ -package eu.ztsh.mockserver.client +package pl.touk.mockserver.client import groovy.transform.CompileStatic import groovy.transform.TypeChecked diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/Method.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/Method.groovy new file mode 100644 index 0000000..82aefea --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/Method.groovy @@ -0,0 +1,12 @@ +package pl.touk.mockserver.client + +enum Method { + POST, + GET, + DELETE, + PUT, + TRACE, + HEAD, + OPTIONS, + PATCH +} diff --git a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/MockAlreadyExists.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockAlreadyExists.groovy similarity index 82% rename from mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/MockAlreadyExists.groovy rename to mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockAlreadyExists.groovy index 40d5dce..f0c00a9 100644 --- a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/MockAlreadyExists.groovy +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockAlreadyExists.groovy @@ -1,4 +1,4 @@ -package eu.ztsh.mockserver.client +package pl.touk.mockserver.client import groovy.transform.CompileStatic import groovy.transform.TypeChecked diff --git a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/MockDoesNotExist.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockDoesNotExist.groovy similarity index 82% rename from mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/MockDoesNotExist.groovy rename to mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockDoesNotExist.groovy index fe93dc0..02d0bee 100644 --- a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/MockDoesNotExist.groovy +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockDoesNotExist.groovy @@ -1,4 +1,4 @@ -package eu.ztsh.mockserver.client +package pl.touk.mockserver.client import groovy.transform.CompileStatic import groovy.transform.TypeChecked diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockEvent.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockEvent.groovy new file mode 100644 index 0000000..8c61ab3 --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockEvent.groovy @@ -0,0 +1,18 @@ +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 + } +} diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockRequest.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockRequest.groovy new file mode 100644 index 0000000..c73c11d --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockRequest.groovy @@ -0,0 +1,22 @@ +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 headers + final Map query + final List path + + MockRequest(String text, Map headers, Map query, List path) { + this.text = text + this.headers = headers + this.query = query + this.path = path + } +} diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockResponse.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockResponse.groovy new file mode 100644 index 0000000..07212fb --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/MockResponse.groovy @@ -0,0 +1,20 @@ +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 headers + + MockResponse(int statusCode, String text, Map headers) { + this.statusCode = statusCode + this.text = text + this.headers = headers + } +} diff --git a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/InvalidMockRequestSchema.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/PeekMockRequestData.groovy similarity index 53% rename from mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/InvalidMockRequestSchema.groovy rename to mockserver-client/src/main/groovy/pl/touk/mockserver/client/PeekMockRequestData.groovy index b1de96f..3d0e546 100644 --- a/mockserver-client/src/main/groovy/eu/ztsh/mockserver/client/InvalidMockRequestSchema.groovy +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/PeekMockRequestData.groovy @@ -1,9 +1,10 @@ -package eu.ztsh.mockserver.client +package pl.touk.mockserver.client import groovy.transform.CompileStatic import groovy.transform.TypeChecked @CompileStatic @TypeChecked -class InvalidMockRequestSchema extends RuntimeException { +class PeekMockRequestData { + String name } diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RegisteredMock.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RegisteredMock.groovy new file mode 100644 index 0000000..f755592 --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RegisteredMock.groovy @@ -0,0 +1,28 @@ +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 + } +} diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RemoteMockServer.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RemoteMockServer.groovy new file mode 100644 index 0000000..6afce3d --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RemoteMockServer.groovy @@ -0,0 +1,119 @@ +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 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 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("""\ + + ${data.name} + ${data.skipReport} + + """, ContentType.create("text/xml", "UTF-8")) + } + + private static StringEntity buildPeekMockRequest(PeekMockRequestData data) { + return new StringEntity("""\ + + ${data.name} + + """, ContentType.create("text/xml", "UTF-8")) + } + + private static StringEntity buildAddMockRequest(AddMockRequestData data) { + return new StringEntity("""\ + + ${data.name} + ${data.path} + ${data.port} + ${data.predicate ? "${data.predicate}" : ''} + ${data.response ? "${data.response}" : ''} + ${data.soap != null ? "${data.soap}" : ''} + ${data.statusCode ? "${data.statusCode}" : ''} + ${data.method ? "${data.method}" : ''} + ${data.responseHeaders ? "${data.responseHeaders}" : ''} + + """, ContentType.create("text/xml", "UTF-8")) + } + + List 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 [] + } +} diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RemoveMockRequestData.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RemoveMockRequestData.groovy new file mode 100644 index 0000000..1d37545 --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RemoveMockRequestData.groovy @@ -0,0 +1,11 @@ +package pl.touk.mockserver.client + +import groovy.transform.CompileStatic +import groovy.transform.TypeChecked + +@CompileStatic +@TypeChecked +class RemoveMockRequestData { + String name + boolean skipReport = false +} diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/Util.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/Util.groovy new file mode 100644 index 0000000..8cffc3b --- /dev/null +++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/Util.groovy @@ -0,0 +1,38 @@ +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 """ + + $request + """ + } + + 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) + } +} diff --git a/mockserver-tests/pom.xml b/mockserver-tests/pom.xml index 2506a7a..97e00da 100644 --- a/mockserver-tests/pom.xml +++ b/mockserver-tests/pom.xml @@ -1,68 +1,49 @@ - - 4.0.0 - + http-mock-server - eu.ztsh.mockserver - 3.0.0-SNAPSHOT + pl.touk.mockserver + 1.1.0 + 4.0.0 mockserver-tests + + clean install + + - eu.ztsh.mockserver - mockserver + org.codehaus.groovy + groovy-all - eu.ztsh.mockserver - mockserver-client + org.spockframework + spock-core - - - org.apache.groovy - groovy - - - - org.apache.httpcomponents - httpclient - - org.slf4j slf4j-api ch.qos.logback - logback-core + logback-classic - - org.spockframework - spock-core + org.apache.httpcomponents + httpclient + + + pl.touk.mockserver + mockserver + ${project.version} + + + pl.touk.mockserver + mockserver-client + ${project.version} - - - - org.codehaus.gmavenplus - gmavenplus-plugin - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.5 - - - **/*Test.java - - - - - - diff --git a/mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerHttpsTest.groovy b/mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerHttpsTest.groovy deleted file mode 100644 index e1ecd95..0000000 --- a/mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerHttpsTest.groovy +++ /dev/null @@ -1,156 +0,0 @@ -package eu.ztsh.mockserver.tests - -import eu.ztsh.mockserver.api.common.Https -import eu.ztsh.mockserver.api.request.AddMock -import eu.ztsh.mockserver.client.RemoteMockServer -import eu.ztsh.mockserver.client.Util -import eu.ztsh.mockserver.server.HttpMockServer -import groovy.xml.slurpersupport.GPathResult -import org.apache.http.client.methods.CloseableHttpResponse -import org.apache.http.client.methods.HttpPost -import org.apache.http.conn.ssl.SSLConnectionSocketFactory -import org.apache.http.conn.ssl.SSLContexts -import org.apache.http.entity.ContentType -import org.apache.http.entity.StringEntity -import org.apache.http.impl.client.CloseableHttpClient -import org.apache.http.impl.client.HttpClients -import spock.lang.AutoCleanup -import spock.lang.Ignore -import spock.lang.Shared -import spock.lang.Specification - -import javax.net.ssl.SSLContext -import javax.net.ssl.SSLHandshakeException -import java.security.KeyStore - -@Ignore -class MockServerHttpsTest extends Specification { - - RemoteMockServer remoteMockServer = new RemoteMockServer('localhost', 19000) - - @AutoCleanup('stop') - HttpMockServer httpMockServer = new HttpMockServer(19000) - - @Shared - SSLContext noClientAuthSslContext = SSLContexts.custom() - .loadTrustMaterial(trustStore()) - .build() - - @Shared - SSLContext trustedCertificateSslContext = SSLContexts.custom() - .loadKeyMaterial(trustedCertificateKeystore(), 'changeit'.toCharArray()) - .loadTrustMaterial(trustStore()) - .build() - - @Shared - SSLContext untrustedCertificateSslContext = SSLContexts.custom() - .loadKeyMaterial(untrustedCertificateKeystore(), 'changeit'.toCharArray()) - .loadTrustMaterial(trustStore()) - .build() - - @Ignore("TODO: SSL peer shut down incorrectly") - def 'should handle HTTPS server' () { - given: - remoteMockServer.addMock(new AddMock( - name: 'testHttps', - path: 'testEndpoint', - port: 10443, - predicate: '''{req -> req.xml.name() == 'request'}''', - response: '''{req -> ""}''', - 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('', 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 -> ""}''', - 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('', 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 -> ""}''', - 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('', 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 - } -} diff --git a/mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerMaxUsesTest.groovy b/mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerMaxUsesTest.groovy deleted file mode 100644 index eda365c..0000000 --- a/mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerMaxUsesTest.groovy +++ /dev/null @@ -1,239 +0,0 @@ -package eu.ztsh.mockserver.tests - - -import org.apache.http.client.methods.CloseableHttpResponse -import org.apache.http.client.methods.HttpPost -import org.apache.http.entity.ContentType -import org.apache.http.entity.StringEntity -import org.apache.http.impl.client.CloseableHttpClient -import org.apache.http.impl.client.HttpClients -import eu.ztsh.mockserver.api.request.AddMock -import eu.ztsh.mockserver.client.RemoteMockServer -import eu.ztsh.mockserver.server.HttpMockServer -import spock.lang.AutoCleanup -import spock.lang.Specification - -class MockServerMaxUsesTest extends Specification { - - RemoteMockServer remoteMockServer - - @AutoCleanup('stop') - HttpMockServer httpMockServer - - CloseableHttpClient client = HttpClients.createDefault() - - def setup() { - httpMockServer = new HttpMockServer(9000) - remoteMockServer = new RemoteMockServer('localhost', 9000) - } - - def 'should return two mocks in order'() { - given:'mock with predicate is given but for only one use' - remoteMockServer.addMock(new AddMock( - name: 'mock1', - path: 'testEndpoint', - port: 9999, - predicate: '''{req -> req.xml.name() == 'request'}''', - response: '''{req -> 'mock1'}''', - maxUses: 1 - )) - and:'mock with the same predicate is given' - remoteMockServer.addMock(new AddMock( - name: 'mock2', - path: 'testEndpoint', - port: 9999, - predicate: '''{req -> req.xml.name() == 'request'}''', - response: '''{req -> 'mock2'}''', - )) - when:'we call the first time' - HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') - restPost.entity = new StringEntity('', 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('', 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('', 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('', 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('', 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('', 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) - } -} diff --git a/mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerIntegrationTest.groovy b/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerIntegrationTest.groovy similarity index 59% rename from mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerIntegrationTest.groovy rename to mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerIntegrationTest.groovy index 038a143..077a893 100644 --- a/mockserver-tests/src/test/groovy/eu/ztsh/mockserver/tests/MockServerIntegrationTest.groovy +++ b/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerIntegrationTest.groovy @@ -1,43 +1,25 @@ -package eu.ztsh.mockserver.tests +package pl.touk.mockserver.tests -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 groovy.util.slurpersupport.GPathResult +import org.apache.http.client.methods.* 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 eu.ztsh.mockserver.api.common.ImportAlias -import eu.ztsh.mockserver.api.common.Method -import eu.ztsh.mockserver.api.request.AddMock -import eu.ztsh.mockserver.api.response.MockEventReport -import eu.ztsh.mockserver.api.response.MockReport -import eu.ztsh.mockserver.client.InvalidMockDefinition -import eu.ztsh.mockserver.client.InvalidMockRequestSchema -import eu.ztsh.mockserver.client.MockAlreadyExists -import eu.ztsh.mockserver.client.MockDoesNotExist -import eu.ztsh.mockserver.client.RemoteMockServer -import eu.ztsh.mockserver.client.Util -import eu.ztsh.mockserver.server.HttpMockServer -import spock.lang.AutoCleanup -import spock.lang.Ignore +import pl.touk.mockserver.client.* +import pl.touk.mockserver.server.HttpMockServer +import spock.lang.Shared import spock.lang.Specification +import spock.lang.Unroll class MockServerIntegrationTest extends Specification { RemoteMockServer remoteMockServer - @AutoCleanup('stop') HttpMockServer httpMockServer + @Shared CloseableHttpClient client = HttpClients.createDefault() def setup() { @@ -45,9 +27,13 @@ class MockServerIntegrationTest extends Specification { remoteMockServer = new RemoteMockServer('localhost', 9000) } + def cleanup() { + httpMockServer.stop() + } + def "should add working rest mock on endpoint"() { expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -66,10 +52,9 @@ class MockServerIntegrationTest extends Specification { remoteMockServer.removeMock('testRest')?.size() == 1 } - @Ignore("TODO: restPostResponse.name()") def "should add working rest mock on endpoint with utf"() { expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRestUtf', path: 'testEndpoint', port: 9999, @@ -91,7 +76,7 @@ class MockServerIntegrationTest extends Specification { def "should add soap mock on endpoint"() { expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testSoap', path: 'testEndpoint', port: 9999, @@ -117,7 +102,7 @@ class MockServerIntegrationTest extends Specification { then: thrown(MockDoesNotExist) expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testSoap', path: 'testEndpoint', port: 9999, @@ -135,7 +120,7 @@ class MockServerIntegrationTest extends Specification { def "should not add mock with existing name"() { expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testSoap', path: 'testEndpoint', port: 9999, @@ -144,7 +129,7 @@ class MockServerIntegrationTest extends Specification { soap: true )) when: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testSoap', path: 'testEndpoint2', port: 9998, @@ -156,23 +141,9 @@ 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 -> ""}''', - soap: false, - schema: 'ble.xsd' - )) - then: - thrown(InvalidMockRequestSchema) - } - def "should not add mock with empty name"() { when: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: '', path: 'testEndpoint2', port: 9998, @@ -186,7 +157,7 @@ class MockServerIntegrationTest extends Specification { def "should add mock after deleting old mock with the same name"() { expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testSoap', path: 'testEndpoint', port: 9999, @@ -197,7 +168,7 @@ class MockServerIntegrationTest extends Specification { and: remoteMockServer.removeMock('testSoap') == [] and: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testSoap', path: 'testEndpoint', port: 9999, @@ -209,14 +180,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 AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, predicate: '''{req -> req.xml.name() == 'request'}''', response: '''{req -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testSoap', path: 'testEndpoint', port: 9999, @@ -241,16 +212,17 @@ class MockServerIntegrationTest extends Specification { soapPostResponse.Body.'goodResponseSoap-request'.size() == 1 } + @Unroll def "should dispatch rest mocks when second on #name"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest1', path: 'test1', port: 9999, predicate: '''{req -> req.xml.name() == 'request1'}''', response: '''{req -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: secondPath, port: secondPort, @@ -279,9 +251,10 @@ class MockServerIntegrationTest extends Specification { 9998 | 'test2' | 'another port and path' } + @Unroll def "should dispatch rest mock with response code"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest1', path: 'test1', port: 9999, @@ -303,7 +276,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 AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest1', path: 'test1', port: 9999, @@ -322,7 +295,7 @@ class MockServerIntegrationTest extends Specification { def "should inform that there was problem during adding mock - invalid port"() { when: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testSoap', path: 'testEndpoint2', port: -1, @@ -336,13 +309,13 @@ class MockServerIntegrationTest extends Specification { def "should dispatch rest mock with get method"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, response: '''{_ -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'testEndpoint', port: 9999, @@ -359,13 +332,13 @@ class MockServerIntegrationTest extends Specification { def "should dispatch rest mock with trace method"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, response: '''{_ -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'testEndpoint', port: 9999, @@ -382,13 +355,13 @@ class MockServerIntegrationTest extends Specification { def "should dispatch rest mock with head method"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, response: '''{_ -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'testEndpoint', port: 9999, @@ -404,13 +377,13 @@ class MockServerIntegrationTest extends Specification { def "should dispatch rest mock with options method"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, response: '''{_ -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'testEndpoint', port: 9999, @@ -426,13 +399,13 @@ class MockServerIntegrationTest extends Specification { def "should dispatch rest mock with put method"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'test1', port: 9999, response: '''{_ -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'test1', port: 9999, @@ -451,13 +424,13 @@ class MockServerIntegrationTest extends Specification { def "should dispatch rest mock with delete method"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'test1', port: 9999, response: '''{_ -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'test1', port: 9999, @@ -474,13 +447,13 @@ class MockServerIntegrationTest extends Specification { def "should dispatch rest mock with patch method"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'test1', port: 9999, response: '''{_ -> ""}''' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'test1', port: 9999, @@ -499,7 +472,7 @@ class MockServerIntegrationTest extends Specification { def "should add mock that return headers"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -519,7 +492,7 @@ class MockServerIntegrationTest extends Specification { def "should add mock that accepts only when certain request headers exists"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -548,7 +521,7 @@ class MockServerIntegrationTest extends Specification { def "should add mock that accepts only when certain query params exists"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -572,7 +545,7 @@ class MockServerIntegrationTest extends Specification { def "should add mock that accepts only when request has specific body"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -597,7 +570,7 @@ class MockServerIntegrationTest extends Specification { def "should add mock which response json to json"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -620,9 +593,9 @@ class MockServerIntegrationTest extends Specification { restPostResponse.name == 'goodResponse-1' } - def "should get list of mocks"() { + def "should get list mocks"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'testEndpoint', port: 9998, @@ -630,58 +603,45 @@ class MockServerIntegrationTest extends Specification { response: '''{ req -> '' }''', responseHeaders: '{ _ -> [a: "b"] }' )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest4', path: 'testEndpoint', - port: 9999, - soap: true, - statusCode: 204, - method: Method.PUT + port: 9999 )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest3', path: 'testEndpoint2', port: 9999 )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest5', path: 'testEndpoint', port: 9999 )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest6', path: 'testEndpoint2', port: 9999 )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', - port: 9999, - schema: 'schema2.xsd', - imports: [new ImportAlias(alias: 'aaa', fullClassName: 'bbb')] + port: 9999 )) remoteMockServer.removeMock('testRest5') - when: - List 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 -> '' }''', 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 props) { - props.each { - assert mockReport."${it.key}" == it.value - } + expect: + remoteMockServer.listMocks() == [ + new RegisteredMock('testRest', 'testEndpoint', 9999, '{ _ -> true }', '''{ _ -> '' }''', '{ _ -> [:] }'), + new RegisteredMock('testRest2', 'testEndpoint', 9998, '''{ req -> req.xml.name() == 'request1'}''', '''{ req -> '' }''', '{ _ -> [a: "b"] }'), + new RegisteredMock('testRest3', 'testEndpoint2', 9999, '{ _ -> true }', '''{ _ -> '' }''', '{ _ -> [:] }'), + new RegisteredMock('testRest4', 'testEndpoint', 9999, '{ _ -> true }', '''{ _ -> '' }''', '{ _ -> [:] }'), + new RegisteredMock('testRest6', 'testEndpoint2', 9999, '{ _ -> true }', '''{ _ -> '' }''', '{ _ -> [:] }') + ] } def "should add mock accepts path certain path params"() { given: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -704,7 +664,7 @@ class MockServerIntegrationTest extends Specification { def "should get mock report when deleting mock"() { expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -714,7 +674,7 @@ class MockServerIntegrationTest extends Specification { responseHeaders: '''{req -> ['aaa':'14']}''', soap: false )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'testEndpoint', port: 9999, @@ -746,40 +706,40 @@ class MockServerIntegrationTest extends Specification { GPathResult restPostResponse3 = Util.extractXmlResponse(response3) restPostResponse3.name() == 'goodResponseRest' when: - List mockEvents1 = remoteMockServer.removeMock('testRest') + List mockEvents1 = remoteMockServer.removeMock('testRest') then: mockEvents1.size() == 2 mockEvents1[0].request.text == '' - !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].request.headers?.keySet()?.empty + mockEvents1[0].request.query == [:] + mockEvents1[0].request.path == ['testEndpoint'] + !mockEvents1[0].response.headers?.keySet()?.empty mockEvents1[0].response.text == '' mockEvents1[0].response.statusCode == 201 mockEvents1[1].request.text == '' - !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].request.headers?.keySet()?.empty + mockEvents1[1].request.query == [:] + mockEvents1[1].request.path == ['testEndpoint', 'hello'] + !mockEvents1[1].response.headers?.keySet()?.empty mockEvents1[1].response.text == '' mockEvents1[1].response.statusCode == 201 when: - List mockEvents2 = remoteMockServer.removeMock('testRest2') + List mockEvents2 = remoteMockServer.removeMock('testRest2') then: mockEvents2.size() == 1 mockEvents2[0].request.text == '' - !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].request.headers?.keySet()?.empty + mockEvents2[0].request.query == [id: '123'] + mockEvents2[0].request.path == ['testEndpoint'] + mockEvents2[0].response.headers.aaa == '15' mockEvents2[0].response.text == '' mockEvents2[0].response.statusCode == 202 } def "should get mock report when peeking mock"() { expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -789,7 +749,7 @@ class MockServerIntegrationTest extends Specification { responseHeaders: '''{req -> ['aaa':'14']}''', soap: false )) - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest2', path: 'testEndpoint', port: 9999, @@ -821,40 +781,41 @@ class MockServerIntegrationTest extends Specification { GPathResult restPostResponse3 = Util.extractXmlResponse(response3) restPostResponse3.name() == 'goodResponseRest' when: - List mockEvents1 = remoteMockServer.peekMock('testRest') + List mockEvents1 = remoteMockServer.peekMock('testRest') then: mockEvents1.size() == 2 mockEvents1[0].request.text == '' - !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].request.headers?.keySet()?.empty + mockEvents1[0].request.query == [:] + mockEvents1[0].request.path == ['testEndpoint'] + !mockEvents1[0].response.headers?.keySet()?.empty mockEvents1[0].response.text == '' mockEvents1[0].response.statusCode == 201 mockEvents1[1].request.text == '' - !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].request.headers?.keySet()?.empty + mockEvents1[1].request.query == [:] + mockEvents1[1].request.path == ['testEndpoint', 'hello'] + !mockEvents1[1].response.headers?.keySet()?.empty mockEvents1[1].response.text == '' mockEvents1[1].response.statusCode == 201 when: - List mockEvents2 = remoteMockServer.peekMock('testRest2') + List mockEvents2 = remoteMockServer.peekMock('testRest2') then: mockEvents2.size() == 1 mockEvents2[0].request.text == '' - !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].request.headers?.keySet()?.empty + mockEvents2[0].request.query == [id: '123'] + mockEvents2[0].request.path == ['testEndpoint'] + mockEvents2[0].response.headers.aaa == '15' mockEvents2[0].response.text == '' mockEvents2[0].response.statusCode == 202 } + @Unroll def "should return mock report with #mockEvents events when deleting mock with flag skip mock = #skipReport"() { expect: - remoteMockServer.addMock(new AddMock( + remoteMockServer.addMock(new AddMockRequestData( name: 'testRest', path: 'testEndpoint', port: 9999, @@ -878,306 +839,4 @@ 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 -> ""}''', - 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 -> ''}''', - soap: false - )) - when: - HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') - restPost.entity = new StringEntity('15unknown', 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('15test', 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 -> ''}''', - soap: true - )) - when: - HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') - restPost.entity = new StringEntity(Util.soap('15unknown'), 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('15test'), 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 -> ''}''', - soap: true - )) - when: - HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') - restPost.entity = new StringEntity(''' - - 15unknown - -''', 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(''' - - 15test - -''', 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 -> ""}''', - soap: false, - imports: [new ImportAlias(alias: 'AAA', fullClassName: 'javax.xml.XMLConstants')] - )) - when: - HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') - restPost.entity = new StringEntity('', 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 -> '' }''', - 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 = 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 -> '' }''', 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 -> ""}''', - soap: false, - preserveHistory: false - )) - when: - HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') - restPost.entity = new StringEntity('', 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') - ] - } } diff --git a/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/ServerMockPT.groovy b/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/ServerMockPT.groovy new file mode 100644 index 0000000..2ee891c --- /dev/null +++ b/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/ServerMockPT.groovy @@ -0,0 +1,53 @@ +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 -> ""}""" + )) + HttpPost restPost = new HttpPost("http://localhost:$port/testEndpoint$endpointNumber") + restPost.entity = new StringEntity("", 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() + } +} diff --git a/mockserver-tests/src/test/resources/keystore.jks b/mockserver-tests/src/test/resources/keystore.jks deleted file mode 100644 index d5e35d1..0000000 Binary files a/mockserver-tests/src/test/resources/keystore.jks and /dev/null differ diff --git a/mockserver-tests/src/test/resources/schema1.xsd b/mockserver-tests/src/test/resources/schema1.xsd deleted file mode 100644 index 4ca340a..0000000 --- a/mockserver-tests/src/test/resources/schema1.xsd +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - diff --git a/mockserver-tests/src/test/resources/schema2.xsd b/mockserver-tests/src/test/resources/schema2.xsd deleted file mode 100644 index 611d010..0000000 --- a/mockserver-tests/src/test/resources/schema2.xsd +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/mockserver-tests/src/test/resources/trusted.jks b/mockserver-tests/src/test/resources/trusted.jks deleted file mode 100644 index e6fa704..0000000 Binary files a/mockserver-tests/src/test/resources/trusted.jks and /dev/null differ diff --git a/mockserver-tests/src/test/resources/truststore.jks b/mockserver-tests/src/test/resources/truststore.jks deleted file mode 100644 index 27a8332..0000000 Binary files a/mockserver-tests/src/test/resources/truststore.jks and /dev/null differ diff --git a/mockserver-tests/src/test/resources/untrusted.jks b/mockserver-tests/src/test/resources/untrusted.jks deleted file mode 100644 index ca94b45..0000000 Binary files a/mockserver-tests/src/test/resources/untrusted.jks and /dev/null differ diff --git a/mockserver/pom.xml b/mockserver/pom.xml index d097048..db754f2 100644 --- a/mockserver/pom.xml +++ b/mockserver/pom.xml @@ -1,95 +1,49 @@ - - 4.0.0 - + - eu.ztsh.mockserver http-mock-server - 3.0.0-SNAPSHOT + pl.touk.mockserver + 1.1.0 + 4.0.0 mockserver - eu.ztsh.mockserver - mockserver-api + org.codehaus.groovy + groovy-all - - - org.apache.groovy - groovy - - - org.apache.groovy - groovy-json - - - org.apache.groovy - groovy-xml - - - - org.glassfish.jaxb - jaxb-core - - - org.glassfish.jaxb - jaxb-runtime - - - - org.apache.commons - commons-lang3 - - org.slf4j slf4j-api ch.qos.logback - logback-core + logback-classic - ch.qos.logback - logback-classic + org.apache.commons + commons-lang3 clean package assembly:single install - - org.codehaus.gmavenplus - gmavenplus-plugin - maven-assembly-plugin - eu.ztsh.mockserver.server.Main + pl.touk.mockserver.server.Main jar-with-dependencies - mockserver-full - false - - - create-archive - package - - single - - - - diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/ContextExecutor.groovy b/mockserver/src/main/groovy/eu/ztsh/mockserver/server/ContextExecutor.groovy deleted file mode 100644 index 6023a1c..0000000 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/ContextExecutor.groovy +++ /dev/null @@ -1,112 +0,0 @@ -package eu.ztsh.mockserver.server - -import com.sun.net.httpserver.HttpExchange -import groovy.transform.PackageScope -import groovy.util.logging.Slf4j -import eu.ztsh.mockserver.api.common.Method - -import java.util.concurrent.CopyOnWriteArrayList - -@Slf4j -@PackageScope -class ContextExecutor { - private final HttpServerWrapper httpServerWrapper - final String path - private final List 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 removeMock(String name) { - Mock mock = mocks.find { it.name == name } - if (mock) { - mocks.remove(mock) - return mock.history - } - return [] - } - - List peekMock(String name) { - Mock mock = mocks.find { it.name == name } - if (mock) { - return mock.history - } - return [] - } - - void addMock(Mock mock) { - mocks << mock - } - - List 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) - } - } -} diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpMockServer.groovy b/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpMockServer.groovy deleted file mode 100644 index eaae94d..0000000 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpMockServer.groovy +++ /dev/null @@ -1,271 +0,0 @@ -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 childServers = new ConcurrentHashMap<>() - private final Set 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 listMocks() { - return childServers.values().collect { it.mocks }.flatten() as TreeSet - } - - 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 mockEvents = skipReport ? [] : childServers.values().collect { - it.removeMock(name) - }.flatten() as List - mockNames.remove(name) - configuration.remove(name) - MockRemoved mockRemoved = new MockRemoved( - mockEvents: createMockEventReports(mockEvents) - ) - createResponse(ex, mockRemoved, 200) - } - - private static List createMockEventReports(List 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 mockEvents = childServers.values().collect { it.peekMock(name) }.flatten() as List - 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() - } -} diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpServerWrapper.groovy b/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpServerWrapper.groovy deleted file mode 100644 index 5d33156..0000000 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpServerWrapper.groovy +++ /dev/null @@ -1,106 +0,0 @@ -package eu.ztsh.mockserver.server - -import com.sun.net.httpserver.HttpHandler -import com.sun.net.httpserver.HttpServer -import com.sun.net.httpserver.HttpsServer -import groovy.transform.PackageScope -import groovy.util.logging.Slf4j -import eu.ztsh.mockserver.api.common.Https - -import javax.net.ssl.KeyManager -import javax.net.ssl.KeyManagerFactory -import javax.net.ssl.SSLContext -import javax.net.ssl.TrustManager -import javax.net.ssl.TrustManagerFactory -import java.security.KeyStore -import java.security.SecureRandom -import java.util.concurrent.Executor - -@Slf4j -@PackageScope -class HttpServerWrapper { - private final HttpServer httpServer - final int port - - private List 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 removeMock(String name) { - return executors.collect { it.removeMock(name) }.flatten() as List - } - - List peekMock(String name) { - return executors.collect { it.peekMock(name) }.flatten() as List - } - - List getMocks() { - return executors.collect { it.mocks }.flatten() as List - } -} diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpsConfig.groovy b/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpsConfig.groovy deleted file mode 100644 index 9d00714..0000000 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/HttpsConfig.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package eu.ztsh.mockserver.server - -import com.sun.net.httpserver.HttpsConfigurator -import com.sun.net.httpserver.HttpsParameters -import groovy.transform.CompileStatic -import eu.ztsh.mockserver.api.common.Https - -import javax.net.ssl.SSLContext -import javax.net.ssl.SSLParameters - -@CompileStatic -class HttpsConfig extends HttpsConfigurator { - private final Https https - - HttpsConfig(SSLContext sslContext, Https https) { - super(sslContext) - this.https = https - } - - @Override - void configure(HttpsParameters httpsParameters) { - SSLContext sslContext = getSSLContext() - SSLParameters sslParameters = sslContext.defaultSSLParameters - sslParameters.needClientAuth = https.requireClientAuth - httpsParameters.needClientAuth = https.requireClientAuth - httpsParameters.SSLParameters = sslParameters - } -} diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Main.groovy b/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Main.groovy deleted file mode 100644 index 916569e..0000000 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Main.groovy +++ /dev/null @@ -1,33 +0,0 @@ -package eu.ztsh.mockserver.server - -import groovy.util.logging.Slf4j - -@Slf4j -class Main { - static void main(String[] args) { - HttpMockServer httpMockServer = startMockServer(args) - - Runtime.runtime.addShutdownHook(new Thread({ - log.info('Http server is stopping...') - httpMockServer.stop() - log.info('Http server is stopped') - } as Runnable)) - - while (true) { - Thread.sleep(10000) - } - } - - private static HttpMockServer startMockServer(String... args) { - switch (args.length) { - case 1: - return new HttpMockServer(args[0] as int, new ConfigObject()) - case 2: - return new HttpMockServer(args[0] as int, new ConfigSlurper().parse(new File(args[1]).toURI().toURL())) - case 3: - return new HttpMockServer(args[0] as int, new ConfigSlurper().parse(new File(args[1]).toURI().toURL()), args[2] as int) - default: - return new HttpMockServer() - } - } -} diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Mock.groovy b/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Mock.groovy deleted file mode 100644 index 821471c..0000000 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Mock.groovy +++ /dev/null @@ -1,199 +0,0 @@ -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 { - 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 history = new CopyOnWriteArrayList<>() - String schema - private Validator validator - Map 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 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) { - """ - - ${request} - """ - } - - 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) - } -} diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Util.groovy b/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Util.groovy deleted file mode 100644 index c51d1f0..0000000 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/Util.groovy +++ /dev/null @@ -1,32 +0,0 @@ -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() - } -} diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/ContextExecutor.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/ContextExecutor.groovy new file mode 100644 index 0000000..d5a8230 --- /dev/null +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/ContextExecutor.groovy @@ -0,0 +1,83 @@ +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 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 removeMock(String name) { + Mock mock = mocks.find { it.name == name } + if (mock) { + mocks.remove(mock) + return mock.history + } + return [] + } + + List peekMock(String name) { + Mock mock = mocks.find { it.name == name } + if (mock) { + return mock.history + } + return [] + } + + void addMock(Mock mock) { + mocks << mock + } + + List getMocks() { + return mocks + } +} diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy new file mode 100644 index 0000000..eae893d --- /dev/null +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy @@ -0,0 +1,191 @@ +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 childServers = new CopyOnWriteArrayList<>() + private final Set 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 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, '', 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 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 mockEvents = childServers.collect { it.peekMock(name) }.flatten() + createResponse(ex, createMockPeekedResponse(mockEvents), 200) + } + + private static String createMockRemovedResponse(List mockEvents) { + StringWriter sw = new StringWriter() + MarkupBuilder builder = new MarkupBuilder(sw) + builder.mockRemoved { + mockEventsToXml(mockEvents, builder) + } + return sw.toString() + } + + private static String createMockPeekedResponse(List mockEvents) { + StringWriter sw = new StringWriter() + MarkupBuilder builder = new MarkupBuilder(sw) + builder.mockPeeked { + mockEventsToXml(mockEvents, builder) + } + return sw.toString() + } + + private static void mockEventsToXml(List 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() + } +} diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpServerWraper.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpServerWraper.groovy new file mode 100644 index 0000000..43692f6 --- /dev/null +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpServerWraper.groovy @@ -0,0 +1,58 @@ +package pl.touk.mockserver.server + +import com.sun.net.httpserver.HttpHandler +import com.sun.net.httpserver.HttpServer +import groovy.transform.PackageScope +import groovy.util.logging.Slf4j + +import java.util.concurrent.Executors + +@Slf4j +@PackageScope +class HttpServerWraper { + private final HttpServer httpServer + final int port + + private List 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 removeMock(String name) { + return executors.collect { it.removeMock(name) }.flatten() + } + + List peekMock(String name) { + return executors.collect { it.peekMock(name) }.flatten() + } + + List getMocks() { + return executors.collect { it.mocks }.flatten() + } +} diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/Main.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/Main.groovy new file mode 100644 index 0000000..759a11a --- /dev/null +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/Main.groovy @@ -0,0 +1,20 @@ +package pl.touk.mockserver.server + +import groovy.util.logging.Slf4j + +@Slf4j +class Main { + static void main(String[] args) { + HttpMockServer httpMockServer = args.length == 1 ? new HttpMockServer(args[0] as int) : new HttpMockServer() + + Runtime.runtime.addShutdownHook(new Thread({ + log.info('Http server is stopping...') + httpMockServer.stop() + log.info('Http server is stopped') + } as Runnable)) + + while (true) { + Thread.sleep(10000) + } + } +} diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy new file mode 100644 index 0000000..61a8132 --- /dev/null +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy @@ -0,0 +1,107 @@ +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 { + 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 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 headers = responseHeaders(request) + MockResponse mockResponse = new MockResponse(statusCode, response, headers) + history << new MockEvent(request, mockResponse) + return mockResponse + } + + private static String wrapSoap(String request) { + """ + + ${request} + """ + } + + 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) + } +} diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockEvent.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/MockEvent.groovy similarity index 88% rename from mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockEvent.groovy rename to mockserver/src/main/groovy/pl/touk/mockserver/server/MockEvent.groovy index 9c429f1..97a46a2 100644 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockEvent.groovy +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/MockEvent.groovy @@ -1,4 +1,4 @@ -package eu.ztsh.mockserver.server +package pl.touk.mockserver.server import groovy.transform.PackageScope diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockRequest.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/MockRequest.groovy similarity index 77% rename from mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockRequest.groovy rename to mockserver/src/main/groovy/pl/touk/mockserver/server/MockRequest.groovy index a714afe..5e6ddc8 100644 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockRequest.groovy +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/MockRequest.groovy @@ -1,11 +1,9 @@ -package eu.ztsh.mockserver.server +package pl.touk.mockserver.server import com.sun.net.httpserver.Headers import groovy.json.JsonSlurper import groovy.transform.PackageScope -import groovy.xml.XmlSlurper -import groovy.xml.slurpersupport.GPathResult -import groovy.xml.XmlUtil +import groovy.util.slurpersupport.GPathResult @PackageScope class MockRequest { @@ -28,9 +26,6 @@ class MockRequest { } private static GPathResult inputToXml(String text) { - if (!text.startsWith('<')) { - return null - } try { return new XmlSlurper().parseText(text) } catch (Exception _) { @@ -40,7 +35,7 @@ class MockRequest { private static GPathResult inputToSoap(GPathResult xml) { try { - if (xml != null && xml.name() == 'Envelope' && xml.Body.size() > 0) { + if (xml.name() == 'Envelope' && xml.Body.size() > 0) { return getSoapBodyContent(xml) } else { return null @@ -51,13 +46,10 @@ class MockRequest { } private static GPathResult getSoapBodyContent(GPathResult xml) { - return xml.Body.'**'[1] as GPathResult + return xml.Body.'**'[1] } private static Object inputToJson(String text) { - if (!text.startsWith('[') && !text.startsWith('{')) { - return null - } try { return new JsonSlurper().parseText(text) } catch (Exception _) { @@ -74,10 +66,7 @@ class MockRequest { private static Map headersToMap(Headers headers) { return headers.collectEntries { [it.key.toLowerCase(), it.value.join(',')] - } as Map + } } - String getTextWithoutSoap() { - return XmlUtil.serialize(soap) - } } diff --git a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockResponse.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/MockResponse.groovy similarity index 90% rename from mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockResponse.groovy rename to mockserver/src/main/groovy/pl/touk/mockserver/server/MockResponse.groovy index 260acce..913f9f3 100644 --- a/mockserver/src/main/groovy/eu/ztsh/mockserver/server/MockResponse.groovy +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/MockResponse.groovy @@ -1,4 +1,4 @@ -package eu.ztsh.mockserver.server +package pl.touk.mockserver.server import groovy.transform.PackageScope diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/Util.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/Util.groovy new file mode 100644 index 0000000..93ab7a5 --- /dev/null +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/Util.groovy @@ -0,0 +1,14 @@ +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() + } + } +} diff --git a/mvnw b/mvnw deleted file mode 100755 index 8d937f4..0000000 --- a/mvnw +++ /dev/null @@ -1,308 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround - done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi -} - -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi -} - -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" -else - log "Couldn't find $wrapperJarPath, downloading it ..." - - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" - - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi - - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." - exit 1 - fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 - exit 1 - fi -fi - -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index f80fbad..0000000 --- a/mvnw.cmd +++ /dev/null @@ -1,205 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B -) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% diff --git a/performance-tests/pom.xml b/performance-tests/pom.xml deleted file mode 100644 index e73db5c..0000000 --- a/performance-tests/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - 4.0.0 - - - eu.ztsh.mockserver - http-mock-server - 3.0.0-SNAPSHOT - - - mockserver-performance-tests - - - - eu.ztsh.mockserver - mockserver - - - eu.ztsh.mockserver - mockserver-client - - - - org.openjdk.jmh - jmh-core - - - org.openjdk.jmh - jmh-generator-annprocess - - - - 1.4.0 - - - - - performance-test - - - - org.codehaus.mojo - exec-maven-plugin - ${exec-maven-plugin.version} - - - run-benchmarks - integration-test - - exec - - - test - java - - -classpath - - org.openjdk.jmh.Main - .* - - - - - - - - - - - \ No newline at end of file diff --git a/performance-tests/src/test/java/eu/ztsh/mockserver/client/MockserverTest.java b/performance-tests/src/test/java/eu/ztsh/mockserver/client/MockserverTest.java deleted file mode 100644 index 2a3b943..0000000 --- a/performance-tests/src/test/java/eu/ztsh/mockserver/client/MockserverTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package eu.ztsh.mockserver.client; - -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.HttpClients; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.infra.ThreadParams; -import eu.ztsh.mockserver.api.request.AddMock; -import eu.ztsh.mockserver.server.HttpMockServer; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -@State(Scope.Benchmark) -@OutputTimeUnit(TimeUnit.SECONDS) -public class MockserverTest { - HttpMockServer httpMockServer; - - int initialPort = 9000; - - @Setup - public void prepareMockServer() { - httpMockServer = new HttpMockServer(9999); - } - - @TearDown - public void stopMockServer() { - httpMockServer.stop(); - } - - @State(Scope.Thread) - public static class TestState { - RemoteMockServer remoteMockServer; - HttpClient httpClient; - int current; - - @Setup - public void prepareMockServer(ThreadParams params) { - remoteMockServer = new RemoteMockServer("localhost", 9999); - httpClient = HttpClients.createDefault(); - current = params.getThreadIndex(); - } - } - - @Benchmark - @Measurement(iterations = 20) - @BenchmarkMode({Mode.AverageTime, Mode.Throughput, Mode.SampleTime}) - @Warmup(iterations = 10) - public void shouldHandleManyRequestsSimultaneously(TestState testState, Blackhole bh) throws IOException { - int current = testState.current; - int endpointNumber = current % 10; - int port = initialPort + (current % 7); - AddMock addMock = new AddMock(); - addMock.setName("testRest" + current); - addMock.setPath("testEndpoint" + endpointNumber); - addMock.setPort(port); - addMock.setPredicate("{req -> req.xml.name() == 'request" + current + "' }"); - addMock.setResponse("{req -> ''}"); - testState.remoteMockServer.addMock(addMock); - HttpPost restPost = new HttpPost("http://localhost:" + port + "/testEndpoint" + endpointNumber); - restPost.setEntity(new StringEntity("", 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(""); - bh.consume(stringResponse); - } - -} diff --git a/performance-tests/src/test/resources/logback.xml b/performance-tests/src/test/resources/logback.xml deleted file mode 100644 index b37e533..0000000 --- a/performance-tests/src/test/resources/logback.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - %highlight(%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n) - - - - - %msg%n - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index a981985..48ad9ac 100644 --- a/pom.xml +++ b/pom.xml @@ -1,90 +1,53 @@ - + 4.0.0 - eu.ztsh.mockserver + pl.touk.mockserver http-mock-server pom - 3.0.0-SNAPSHOT - + 1.1.0 mockserver-client mockserver mockserver-tests - mockserver-api - performance-tests - 11 - ${java.version} - ${java.version} UTF-8 - UTF-8 - 4.0.12 - 4.5.13 - 2.2-groovy-4.0 + 3.1 + 2.4.1 + 4.3.5 + 1.0-groovy-2.4 3.3.2 - 1.7.30 - 1.3.12 - 1.18.26 - 4.0.4 - - true - 1.37 - 3.0.2 - 3.1.0 + 1.7.7 + 1.0.13 + + scm:git:ssh://gerrit.touk.pl:29418/integracja/http-mock-server + scm:git:ssh://gerrit.touk.pl:29418/integracja/http-mock-server + HEAD + + - eu.ztsh.mockserver - mockserver-api - ${project.version} - - - eu.ztsh.mockserver - mockserver - ${project.version} - - - eu.ztsh.mockserver - mockserver-client - ${project.version} - - - - org.glassfish.jaxb - jaxb-bom - ${jaxb.version} - pom - import - - - - org.apache.groovy - groovy + org.codehaus.groovy + groovy-all ${groovy.version} - - org.apache.groovy - groovy-json - ${groovy.version} - - - org.apache.groovy - groovy-xml - ${groovy.version} - - org.apache.httpcomponents httpclient ${httpclient.version} + + org.spockframework + spock-core + ${spock-core.version} + test + org.apache.commons commons-lang3 @@ -95,66 +58,44 @@ slf4j-api ${slf4j-api.version} - - ch.qos.logback - logback-core - ${logback.version} - ch.qos.logback logback-classic - ${logback.version} - - - org.projectlombok - lombok - ${lombok.version} - - - - org.spockframework - spock-core - ${spock-core.version} - test - - - org.openjdk.jmh - jmh-core - ${jmh.version} - test - - - org.openjdk.jmh - jmh-generator-annprocess - ${jmh.version} - test + ${logback-classic.version} - - - - org.codehaus.mojo - jaxb2-maven-plugin - ${jaxb2-maven-plugin.version} - - - org.codehaus.gmavenplus - gmavenplus-plugin - ${gmavenplus-plugin.version} - - - - compile - compileTests - - - - - - + clean install + + + org.codehaus.gmavenplus + gmavenplus-plugin + 1.4 + + + + compile + testCompile + + + + + + + + touk.nexus.release + TouK Virtual Repository + http://nexus.touk.pl/nexus/content/repositories/releases + + + touk.nexus.snapshots + TouK Virtual Repository + http://nexus.touk.pl/nexus/content/repositories/snapshots + + +