diff --git a/Dockerfile b/Dockerfile
index fc44fd1..87dd9dd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,4 +8,4 @@ RUN mkdir /externalSchema
VOLUME /externalSchema
-CMD java -cp /mockserver.jar:/externalSchema -jar /mockserver.jar
+CMD java -cp /mockserver.jar:/externalSchema pl.touk.mockserver.server.Main
diff --git a/README.md b/README.md
index a06f405..139c19e 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,71 @@
[](https://travis-ci.org/TouK/http-mock-server)
-# HTTP MOCK SERVER
+HTTP MOCK SERVER
+================
-## Create server jar
+Http Mock Server allows to mock HTTP request using groovy closures.
+
+Create server jar
+-----------------
```
cd mockserver
mvn clean package assembly:single
```
-## Start server
+Start server
+------------
### Native start
```
-java -jar mockserver.jar [PORT]
+java -jar mockserver-full.jar [PORT] [CONFIGURATION_FILE]
```
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'
+}
+testRest {
+ imports {
+ aaa='bbb'
+ ccc='bla'
+ }
+ port=10001
+ path='testEndpoint'
+ name='testRest'
+}
+```
+
### Start with docker
Docker and docker-compose is needed.
@@ -28,7 +75,8 @@ Docker and docker-compose is needed.
docker-compose up -d
```
-## Create mock on server
+Create mock on server
+---------------------
### Via client
@@ -47,11 +95,11 @@ remoteMockServer.addMock(new AddMock(
schema: ...
))
```
+
### Via HTTP
Send POST request to localhost:/serverControl
-
```xml
...
@@ -64,33 +112,35 @@ Send POST request to localhost:/serverControl
...
...
...
+
```
### 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, expected http method of request, default POST
-* responseHeaders - groovyClosure as string which must evaluate to Map which will be added to response headers, default { _ -> [:] }
-* schema - path to xsd schema file on mockserver classpath; default empty, so no vallidation of request is performed; if validation fails then response has got status 400 and response is raw message from validator
+- name - name of mock, must be unique
+- path - path on which mock should be created
+- port - inteer, port on which mock should be created, cannot be the same as mock server port
+- predicate - groovy closure as string which must evaluate to true, when request object will be given to satisfy mock, optional, default {_ -> true}
+- response - groovy closure as string which must evaluate to string which will be response of mock when predicate is satisfied, optional, default { _ -> '' }
+- soap - true or false, is request and response should be wrapped in soap Envelope and Body elements, default false
+- statusCode - integer, status code of response when predicate is satisfied, default 200
+- method - POST|PUT|DELETE|GET|TRACE|OPTION|HEAD, expected http method of request, default POST
+- responseHeaders - groovyClosure as string which must evaluate to Map which will be added to response headers, default { _ -> \[:] }
+- schema - path to xsd schema file on mockserver classpath; default empty, so no vallidation of request is performed; if validation fails then response has got status 400 and response is raw message from validator
+- 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
### Closures request properties
In closures input parameter (called req) contains properties:
-* text - request body as java.util.String
-* headers - java.util.Map with request headers
-* query - java.util.Map with query parameters
-* xml - groovy.util.slurpersupport.GPathResult created from request body (if request body is valid xml)
-* soap - groovy.util.slurpersupport.GPathResult created from request body without Envelope and Body elements (if request body is valid soap xml)
-* json - java.lang.Object created from request body (if request body is valid json)
-* path - java.util.List 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:
@@ -104,7 +154,9 @@ Response with error message if failure:
...
```
-## Peek mock
+Peek mock
+---------
+
Mock could be peeked to get get report of its invocations.
### Via client
@@ -114,6 +166,7 @@ List mockEvents = remoteMockServer.peekMock('...')
```
### Via HTTP
+
Send POST request to localhost:/serverControl
```xml
@@ -160,7 +213,8 @@ Response with error message if failure:
...
```
-## Remove mock
+Remove mock
+-----------
When mock was used it could be unregistered by name. It also optionally returns report of mock invocations if second parameter is true.
@@ -169,7 +223,9 @@ When mock was used it could be unregistered by name. It also optionally returns
```java
List mockEvents = remoteMockServer.removeMock('...', ...)
```
+
### Via HTTP
+
Send POST request to localhost:/serverControl
```xml
@@ -223,7 +279,8 @@ Response with error message if failure:
...
```
-## List mocks definitions
+List mocks definitions
+----------------------
### Via client
@@ -249,12 +306,69 @@ Response:
...
...
...
+
...
```
-## Remote repository
+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.touk.pl`.
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
index ffbc9a0..91ebc33 100644
--- a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RemoteMockServer.groovy
+++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/RemoteMockServer.groovy
@@ -11,7 +11,11 @@ import pl.touk.mockserver.api.request.AddMock
import pl.touk.mockserver.api.request.MockServerRequest
import pl.touk.mockserver.api.request.PeekMock
import pl.touk.mockserver.api.request.RemoveMock
-import pl.touk.mockserver.api.response.*
+import pl.touk.mockserver.api.response.MockEventReport
+import pl.touk.mockserver.api.response.MockPeeked
+import pl.touk.mockserver.api.response.MockRemoved
+import pl.touk.mockserver.api.response.MockReport
+import pl.touk.mockserver.api.response.Mocks
import javax.xml.bind.JAXBContext
@@ -47,6 +51,13 @@ class RemoteMockServer {
return mockPeeked.mockEvents ?: []
}
+ ConfigObject getConfiguration() {
+ HttpGet get = new HttpGet(address + '/configuration')
+ CloseableHttpResponse response = client.execute(get)
+ String configuration = Util.extractStringResponse(response)
+ return new ConfigSlurper().parse(configuration)
+ }
+
private static StringEntity buildRemoveMockRequest(RemoveMock data) {
return new StringEntity(marshallRequest(data), ContentType.create("text/xml", "UTF-8"))
}
diff --git a/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerIntegrationTest.groovy b/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerIntegrationTest.groovy
index 1412de1..6476a96 100644
--- a/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerIntegrationTest.groovy
+++ b/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerIntegrationTest.groovy
@@ -1029,4 +1029,66 @@ class MockServerIntegrationTest extends Specification {
expect:
remoteMockServer.removeMock('testRest')?.size() == 1
}
+
+ def "should get configuration of mocks and reconfigure new mock server based on it"() {
+ given:
+ remoteMockServer.addMock(new AddMock(
+ name: 'testRest2',
+ path: 'testEndpoint',
+ port: 9998,
+ predicate: '''{ req -> req.xml.name() == 'request1'}''',
+ response: '''{ req -> '' }''',
+ 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')
+ ]
+ ))
+ 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'])
+ 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'
+ }
+
}
diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy
index c25b990..171d72a 100644
--- a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy
+++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy
@@ -3,6 +3,7 @@ package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpExchange
import groovy.util.logging.Slf4j
import pl.touk.mockserver.api.common.ImportAlias
+import pl.touk.mockserver.api.common.Method
import pl.touk.mockserver.api.request.AddMock
import pl.touk.mockserver.api.request.MockServerRequest
import pl.touk.mockserver.api.request.PeekMock
@@ -30,18 +31,27 @@ class HttpMockServer {
private final HttpServerWraper httpServerWraper
private final Map childServers = new ConcurrentHashMap<>()
private final Set mockNames = new CopyOnWriteArraySet<>()
+ private final ConfigObject configuration = new ConfigObject()
private static
final JAXBContext requestJaxbContext = JAXBContext.newInstance(AddMock.package.name, AddMock.classLoader)
- HttpMockServer(int port = 9999) {
+ HttpMockServer(int port = 9999, ConfigObject initialConfiguration = new ConfigObject()) {
httpServerWraper = new HttpServerWraper(port)
+ initialConfiguration.values()?.each { ConfigObject co ->
+ addMock(co)
+ }
+
httpServerWraper.createContext('/serverControl', {
HttpExchange ex ->
try {
if (ex.requestMethod == 'GET') {
- listMocks(ex)
+ 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) {
@@ -95,10 +105,41 @@ class HttpMockServer {
Mock mock = mockFromRequest(request)
HttpServerWraper child = getOrCreateChildServer(mock.port)
child.addMock(mock)
+ saveConfiguration(request)
mockNames << name
createResponse(ex, new MockAdded(), 200)
}
+ private void addMock(ConfigObject co) {
+ String name = co.name
+ if (name in mockNames) {
+ throw new RuntimeException('mock already registered')
+ }
+ Mock mock = mockFromConfig(co)
+ HttpServerWraper child = getOrCreateChildServer(mock.port)
+ 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] } ?: [:]
@@ -112,6 +153,19 @@ class HttpMockServer {
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
+ return mock
+ }
+
private HttpServerWraper getOrCreateChildServer(int mockPort) {
HttpServerWraper child = childServers[mockPort]
if (!child) {
@@ -132,6 +186,7 @@ class HttpMockServer {
it.removeMock(name)
}.flatten() as List
mockNames.remove(name)
+ configuration.remove(name)
MockRemoved mockRemoved = new MockRemoved(
mockEvents: createMockEventReports(mockEvents)
)
diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/Main.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/Main.groovy
index 759a11a..aba7f6e 100644
--- a/mockserver/src/main/groovy/pl/touk/mockserver/server/Main.groovy
+++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/Main.groovy
@@ -5,7 +5,7 @@ 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()
+ HttpMockServer httpMockServer = startMockServer(args)
Runtime.runtime.addShutdownHook(new Thread({
log.info('Http server is stopping...')
@@ -17,4 +17,15 @@ class Main {
Thread.sleep(10000)
}
}
+
+ private static HttpMockServer startMockServer(String... args) {
+ switch (args.length) {
+ case 1:
+ return new HttpMockServer(args[0] as int)
+ case 2:
+ return new HttpMockServer(args[0] as int, new ConfigSlurper().parse(new File(args[1]).toURI().toURL()))
+ default:
+ return new HttpMockServer()
+ }
+ }
}