diff --git a/mockserver-api/src/main/xsd/pl/touk/mockserver/api/request.xsd b/mockserver-api/src/main/xsd/pl/touk/mockserver/api/request.xsd
index fb4afca..10c7190 100644
--- a/mockserver-api/src/main/xsd/pl/touk/mockserver/api/request.xsd
+++ b/mockserver-api/src/main/xsd/pl/touk/mockserver/api/request.xsd
@@ -21,6 +21,7 @@
+
diff --git a/mockserver-api/src/main/xsd/pl/touk/mockserver/api/response.xsd b/mockserver-api/src/main/xsd/pl/touk/mockserver/api/response.xsd
index 21ffd9b..9e630dc 100644
--- a/mockserver-api/src/main/xsd/pl/touk/mockserver/api/response.xsd
+++ b/mockserver-api/src/main/xsd/pl/touk/mockserver/api/response.xsd
@@ -108,6 +108,7 @@
+
diff --git a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/InvalidMockRequestSchema.groovy b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/InvalidMockRequestSchema.groovy
new file mode 100644
index 0000000..ff3d401
--- /dev/null
+++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/InvalidMockRequestSchema.groovy
@@ -0,0 +1,9 @@
+package pl.touk.mockserver.client
+
+import groovy.transform.CompileStatic
+import groovy.transform.TypeChecked
+
+@CompileStatic
+@TypeChecked
+class InvalidMockRequestSchema extends RuntimeException {
+}
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
index 966625f..3a427d6 100644
--- a/mockserver-client/src/main/groovy/pl/touk/mockserver/client/Util.groovy
+++ b/mockserver-client/src/main/groovy/pl/touk/mockserver/client/Util.groovy
@@ -42,6 +42,9 @@ class Util {
if (message == 'mock not registered') {
throw new MockDoesNotExist()
}
+ if (message == 'mock request schema is invalid schema') {
+ throw new InvalidMockRequestSchema()
+ }
throw new InvalidMockDefinition(message)
}
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 3dd7ec4..fa61e4a 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
@@ -7,11 +7,10 @@ import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils
-import pl.touk.mockserver.api.request.AddMock
import pl.touk.mockserver.api.common.Method
+import pl.touk.mockserver.api.request.AddMock
import pl.touk.mockserver.api.response.MockEventReport
import pl.touk.mockserver.api.response.MockReport
-import pl.touk.mockserver.api.response.Parameter
import pl.touk.mockserver.client.*
import pl.touk.mockserver.server.HttpMockServer
import spock.lang.Shared
@@ -146,6 +145,20 @@ 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(
@@ -634,21 +647,22 @@ class MockServerIntegrationTest extends Specification {
remoteMockServer.addMock(new AddMock(
name: 'testRest',
path: 'testEndpoint',
- port: 9999
+ port: 9999,
+ schema: 'schema2.xsd'
))
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])
+ 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])
}
- private void assertMockReport( MockReport mockReport, Map props) {
+ private static void assertMockReport(MockReport mockReport, Map props) {
props.each {
assert mockReport."${it.key}" == it.value
}
@@ -820,9 +834,9 @@ class MockServerIntegrationTest extends Specification {
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.queryParams.queryParams.find { it.name == 'id' }?.value == '123'
mockEvents2[0].request.path.pathParts == ['testEndpoint']
- mockEvents2[0].response.headers.headers.find {it.name == 'aaa'}?.value == '15'
+ mockEvents2[0].response.headers.headers.find { it.name == 'aaa' }?.value == '15'
mockEvents2[0].response.text == ''
mockEvents2[0].response.statusCode == 202
}
@@ -881,4 +895,100 @@ class MockServerIntegrationTest extends Specification {
'''{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
+ }
}
diff --git a/mockserver-tests/src/test/resources/schema1.xsd b/mockserver-tests/src/test/resources/schema1.xsd
new file mode 100644
index 0000000..4ca340a
--- /dev/null
+++ b/mockserver-tests/src/test/resources/schema1.xsd
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mockserver-tests/src/test/resources/schema2.xsd b/mockserver-tests/src/test/resources/schema2.xsd
new file mode 100644
index 0000000..611d010
--- /dev/null
+++ b/mockserver-tests/src/test/resources/schema2.xsd
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
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 0b601ff..8eaab6d 100644
--- a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy
+++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy
@@ -64,7 +64,8 @@ class HttpMockServer {
responseHeaders: it.responseHeadersClosureText,
soap: it.soap,
method: it.method,
- statusCode: it.statusCode as int
+ statusCode: it.statusCode as int,
+ schema: it.schema
)
}
)
@@ -95,6 +96,7 @@ class HttpMockServer {
mock.statusCode = request.statusCode
mock.method = request.method
mock.responseHeaders = request.responseHeaders
+ mock.schema = request.schema
return mock
}
@@ -135,12 +137,12 @@ class HttpMockServer {
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)
+ 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 {
+ headers: new MockResponseReport.Headers(headers: it.response.headers.collect {
new Parameter(name: it.key, value: it.value)
})
)
@@ -162,6 +164,7 @@ class HttpMockServer {
}
private static void createErrorResponse(HttpExchange ex, Exception e) {
+ log.warn('Exception occured', e)
createResponse(ex, new ExceptionOccured(value: e.message), 400)
}
diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy
index 32bad68..15b538f 100644
--- a/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy
+++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy
@@ -5,6 +5,10 @@ import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import pl.touk.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
@@ -25,6 +29,8 @@ class Mock implements Comparable {
Method method = Method.POST
int counter = 0
final List history = new CopyOnWriteArrayList<>()
+ String schema
+ private Validator validator
Mock(String name, String path, int port) {
if (!(name)) {
@@ -41,6 +47,20 @@ class Mock implements Comparable {
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, [:])
+ history << new MockEvent(request, response)
+ return response
+ }
+ }
++counter
String responseText = response(request)
String response = soap ? wrapSoap(responseText) : responseText
@@ -106,4 +126,17 @@ class Mock implements Comparable {
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(new File(this.class.getResource("/$schema").path))
+ .newValidator()
+ } catch (Exception e) {
+ throw new RuntimeException('mock request schema is invalid schema', e)
+ }
+ }
+ }
}
diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/MockRequest.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/MockRequest.groovy
index c4ebe01..f8dc561 100644
--- a/mockserver/src/main/groovy/pl/touk/mockserver/server/MockRequest.groovy
+++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/MockRequest.groovy
@@ -4,6 +4,7 @@ import com.sun.net.httpserver.Headers
import groovy.json.JsonSlurper
import groovy.transform.PackageScope
import groovy.util.slurpersupport.GPathResult
+import groovy.xml.XmlUtil
@PackageScope
class MockRequest {
@@ -69,4 +70,7 @@ class MockRequest {
} as Map
}
+ String getTextWithoutSoap() {
+ return XmlUtil.serialize(soap)
+ }
}