From 0805de459d40e9ab7b6cde22548468a398b0d404 Mon Sep 17 00:00:00 2001 From: Dominik Adam Przybysz Date: Sat, 13 Dec 2014 17:44:53 +0100 Subject: [PATCH] Add json support --- .../mockserver/server/ContextExecutor.groovy | 28 +--- .../mockserver/server/HttpMockServer.groovy | 8 - .../pl/touk/mockserver/server/Mock.groovy | 8 +- .../pl/touk/mockserver/server/Request.groovy | 68 ++++++++ .../client/AddMockRequestData.groovy | 14 +- .../client/ControlServerClient.groovy | 2 - .../pl/touk/mockserver/client/Util.groovy | 8 +- .../server/MockServerIntegrationTest.groovy | 151 ++++++++++++------ 8 files changed, 184 insertions(+), 103 deletions(-) create mode 100644 src/main/groovy/pl/touk/mockserver/server/Request.groovy diff --git a/src/main/groovy/pl/touk/mockserver/server/ContextExecutor.groovy b/src/main/groovy/pl/touk/mockserver/server/ContextExecutor.groovy index d67727c..923125d 100644 --- a/src/main/groovy/pl/touk/mockserver/server/ContextExecutor.groovy +++ b/src/main/groovy/pl/touk/mockserver/server/ContextExecutor.groovy @@ -16,32 +16,16 @@ class ContextExecutor { this.mocks = new CopyOnWriteArrayList<>([initialMock]) httpServerWraper.createContext(path, { HttpExchange ex -> - String input = ex.requestBody.text - Map queryParams = ex.requestURI.query?.split('&')?.collectEntries { - String[] keyValue = it.split('='); [(keyValue[0]): keyValue[1]] - } ?: [:] - Map headers = ex.requestHeaders.collectEntries { - [it.key.toLowerCase(), it.value.join(',')] - } + Request request = new Request(ex.requestBody.text, ex.requestHeaders, ex.requestURI.query) println "Mock received input" for (Mock mock : mocks) { try { - GPathResult xml = input ? new XmlSlurper().parseText(input) : null - if (mock.soap) { - if (xml.name() == 'Envelope' && xml.Body.size() > 0) { - xml = getSoapBodyContent(xml) - } else { - continue - } - } if (ex.requestMethod == mock.method && - mock.predicate(xml) && - mock.requestHeaders(headers) && - mock.queryParams(queryParams)) { + mock.predicate(request)) { println "Mock ${mock.name} invoked" ++mock.counter - String response = mock.responseOk(xml) - mock.responseHeaders(xml).each { + String response = mock.responseOk(request) + mock.responseHeaders(request).each { ex.responseHeaders.add(it.key as String, it.value as String) } ex.sendResponseHeaders(mock.statusCode, response ? 0 : -1) @@ -61,10 +45,6 @@ class ContextExecutor { }) } - private static GPathResult getSoapBodyContent(GPathResult xml) { - return xml.Body.'**'[1] - } - int removeMock(String name) { Mock mock = mocks.find { it.name == name } if (mock) { diff --git a/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy b/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy index 6515bc8..ca53bb0 100644 --- a/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy +++ b/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy @@ -65,14 +65,6 @@ class HttpMockServer { if(responseHeaders){ mock.responseHeaders = Eval.me(responseHeaders) as Closure } - String requestHeaders = request.requestHeaders - if(requestHeaders){ - mock.requestHeaders = Eval.me(requestHeaders) as Closure - } - String queryParams = request.queryParams - if(queryParams){ - mock.queryParams = Eval.me(queryParams) as Closure - } HttpServerWraper child = childServers.find { it.port == mockPort } if (!child) { child = new HttpServerWraper(mockPort) diff --git a/src/main/groovy/pl/touk/mockserver/server/Mock.groovy b/src/main/groovy/pl/touk/mockserver/server/Mock.groovy index 3f42636..4b66cb6 100644 --- a/src/main/groovy/pl/touk/mockserver/server/Mock.groovy +++ b/src/main/groovy/pl/touk/mockserver/server/Mock.groovy @@ -7,14 +7,12 @@ class Mock { final String name final String path final int port - Closure predicate = { xml -> true } - Closure responseOk = { xml -> '' } + Closure predicate = { _ -> true } + Closure responseOk = { _ -> '' } boolean soap = false int statusCode = 200 String method = 'POST' - Closure requestHeaders = {hs -> true} - Closure responseHeaders = {xml -> [:]} - Closure queryParams = {qs -> true} + Closure responseHeaders = {_ -> [:]} int counter = 0 Mock(String name, String path, int port) { diff --git a/src/main/groovy/pl/touk/mockserver/server/Request.groovy b/src/main/groovy/pl/touk/mockserver/server/Request.groovy new file mode 100644 index 0000000..a5e954c --- /dev/null +++ b/src/main/groovy/pl/touk/mockserver/server/Request.groovy @@ -0,0 +1,68 @@ +package pl.touk.mockserver.server + +import com.sun.net.httpserver.Headers +import groovy.json.JsonSlurper +import groovy.util.slurpersupport.GPathResult + +class Request { + final String text + final Map headers + final Map query + final GPathResult xml + final GPathResult soap + final Object json + + Request(String text, Headers headers, String query) { + this.text = text + this.headers = headersToMap(headers) + this.query = queryParamsToMap(query) + this.xml = inputToXml(text) + this.soap = inputToSoap(xml) + this.json= inputToJson(text) + } + + private static GPathResult inputToXml(String text) { + try{ + return new XmlSlurper().parseText(text) + }catch (Exception _){ + return null + } + } + + private static GPathResult inputToSoap(GPathResult xml) { + try{ + if (xml.name() == 'Envelope' && xml.Body.size() > 0) { + return getSoapBodyContent(xml) + } else { + return null + } + }catch (Exception _){ + return null + } + } + + private static GPathResult getSoapBodyContent(GPathResult xml) { + return xml.Body.'**'[1] + } + + private static Object inputToJson(String text) { + try{ + return new JsonSlurper().parseText(text) + }catch (Exception _){ + return null + } + } + + private static Map queryParamsToMap(String query) { + return query?.split('&')?.collectEntries { + String[] keyValue = it.split('='); [(keyValue[0]): keyValue[1]] + } ?: [:] + } + + private static Map headersToMap(Headers headers) { + return headers.collectEntries { + [it.key.toLowerCase(), it.value.join(',')] + } + } + +} diff --git a/src/test/groovy/pl/touk/mockserver/client/AddMockRequestData.groovy b/src/test/groovy/pl/touk/mockserver/client/AddMockRequestData.groovy index a41fde2..4e3dfbe 100644 --- a/src/test/groovy/pl/touk/mockserver/client/AddMockRequestData.groovy +++ b/src/test/groovy/pl/touk/mockserver/client/AddMockRequestData.groovy @@ -1,7 +1,11 @@ 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 @@ -12,8 +16,6 @@ class AddMockRequestData { Integer statusCode Method method String responseHeaders - String requestHeaders - String queryParams void setPredicate(String predicate) { this.predicate = StringEscapeUtils.escapeXml11(predicate) @@ -27,14 +29,6 @@ class AddMockRequestData { this.responseHeaders = StringEscapeUtils.escapeXml11(responseHeaders) } - void setRequestHeaders(String requestHeaders) { - this.requestHeaders = StringEscapeUtils.escapeXml11(requestHeaders) - } - - void setQueryParams(String queryParams) { - this.queryParams = StringEscapeUtils.escapeXml11(queryParams) - } - enum Method { POST, GET, diff --git a/src/test/groovy/pl/touk/mockserver/client/ControlServerClient.groovy b/src/test/groovy/pl/touk/mockserver/client/ControlServerClient.groovy index fcbfc5c..4e6f021 100644 --- a/src/test/groovy/pl/touk/mockserver/client/ControlServerClient.groovy +++ b/src/test/groovy/pl/touk/mockserver/client/ControlServerClient.groovy @@ -61,8 +61,6 @@ class ControlServerClient { ${data.statusCode ? "${data.statusCode}" : ''} ${data.method ? "${data.method}" : ''} ${data.responseHeaders ? "${data.responseHeaders}" : ''} - ${data.requestHeaders ? "${data.requestHeaders}" : ''} - ${data.queryParams ? "${data.queryParams}" : ''} """, ContentType.create("text/xml", "UTF-8")) } diff --git a/src/test/groovy/pl/touk/mockserver/client/Util.groovy b/src/test/groovy/pl/touk/mockserver/client/Util.groovy index 5c223ab..889072a 100644 --- a/src/test/groovy/pl/touk/mockserver/client/Util.groovy +++ b/src/test/groovy/pl/touk/mockserver/client/Util.groovy @@ -1,5 +1,6 @@ package pl.touk.mockserver.client +import groovy.json.JsonSlurper import groovy.transform.PackageScope import groovy.util.slurpersupport.GPathResult import org.apache.http.HttpEntity @@ -7,7 +8,6 @@ import org.apache.http.client.methods.CloseableHttpResponse import org.apache.http.util.EntityUtils class Util { - @PackageScope static GPathResult extractXmlResponse(CloseableHttpResponse response){ HttpEntity entity = response.entity GPathResult xml = new XmlSlurper().parseText(EntityUtils.toString(entity)) @@ -22,4 +22,10 @@ class Util { """ } + static Object extractJsonResponse(CloseableHttpResponse response) { + HttpEntity entity = response.entity + Object json = new JsonSlurper().parseText(EntityUtils.toString(entity)) + EntityUtils.consumeQuietly(entity) + return json + } } diff --git a/src/test/groovy/pl/touk/mockserver/server/MockServerIntegrationTest.groovy b/src/test/groovy/pl/touk/mockserver/server/MockServerIntegrationTest.groovy index c34abbe..fc6828e 100644 --- a/src/test/groovy/pl/touk/mockserver/server/MockServerIntegrationTest.groovy +++ b/src/test/groovy/pl/touk/mockserver/server/MockServerIntegrationTest.groovy @@ -36,8 +36,8 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.xml.name() == 'request'}''', + response: '''{req -> ""}''', soap: false )) when: @@ -57,8 +57,8 @@ class MockServerIntegrationTest extends Specification { name: 'testSoap', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.soap.name() == 'request'}''', + response: '''{req -> ""}''', soap: true )) when: @@ -83,8 +83,8 @@ class MockServerIntegrationTest extends Specification { name: 'testSoap', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.xml.name() == 'request'}''', + response: '''{req -> ""}''', soap: true )) and: @@ -101,8 +101,8 @@ class MockServerIntegrationTest extends Specification { name: 'testSoap', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.xml.name() == 'request'}''', + response: '''{req -> ""}''', soap: true )) when: @@ -110,8 +110,8 @@ class MockServerIntegrationTest extends Specification { name: 'testSoap', path: '/testEndpoint2', port: 9998, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.xml.name() == 'request'}''', + response: '''{req -> ""}''', soap: true )) then: @@ -124,8 +124,8 @@ class MockServerIntegrationTest extends Specification { name: 'testSoap', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.xml.name() == 'request'}''', + response: '''{req -> ""}''', soap: true )) and: @@ -135,8 +135,8 @@ class MockServerIntegrationTest extends Specification { name: 'testSoap', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request2'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.xml.name() == 'request2'}''', + response: '''{req -> ""}''', soap: true )) } @@ -147,15 +147,15 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''' + predicate: '''{req -> req.xml.name() == 'request'}''', + response: '''{req -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testSoap', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.soap.name() == 'request'}''', + response: '''{req -> ""}''', soap: true )) HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') @@ -182,15 +182,15 @@ class MockServerIntegrationTest extends Specification { name: 'testRest1', path: '/test1', port: 9999, - predicate: '''{xml -> xml.name() == 'request1'}''', - response: '''{xml -> ""}''' + predicate: '''{req -> req.xml.name() == 'request1'}''', + response: '''{req -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testRest2', path: secondPath, port: secondPort, - predicate: '''{xml -> xml.name() == 'request2'}''', - response: '''{xml -> ""}''' + predicate: '''{req -> req.xml.name() == 'request2'}''', + response: '''{req -> ""}''' )) HttpPost firstRequest = new HttpPost('http://localhost:9999/test1') firstRequest.entity = new StringEntity('', ContentType.create("text/xml", "UTF-8")) @@ -243,8 +243,8 @@ class MockServerIntegrationTest extends Specification { name: 'testRest1', path: '/test1', port: 9999, - predicate: '''{xml -> xml.name() == 'request2'}''', - response: '''{xml -> ""}''' + predicate: '''{req -> req.xml.name() == 'request2'}''', + response: '''{req -> ""}''' )) HttpPost request = new HttpPost('http://localhost:9999/test1') request.entity = new StringEntity('', ContentType.create("text/xml", "UTF-8")) @@ -262,8 +262,8 @@ class MockServerIntegrationTest extends Specification { name: 'testSoap', path: '/testEndpoint2', port: -1, - predicate: '''{xml -> true}''', - response: '''{xml -> ""}''', + predicate: '''{_ -> true}''', + response: '''{req -> ""}''', soap: true )) then: @@ -276,13 +276,13 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - response: '''{xml -> ""}''' + response: '''{_ -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testRest2', path: '/testEndpoint', port: 9999, - response: '''{xml -> ""}''', + response: '''{_ -> ""}''', method: AddMockRequestData.Method.GET )) HttpGet restGet = new HttpGet('http://localhost:9999/testEndpoint') @@ -299,13 +299,13 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - response: '''{xml -> ""}''' + response: '''{_ -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testRest2', path: '/testEndpoint', port: 9999, - response: '''{xml -> ""}''', + response: '''{_ -> ""}''', method: AddMockRequestData.Method.TRACE )) HttpTrace restTrace = new HttpTrace('http://localhost:9999/testEndpoint') @@ -322,7 +322,7 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - response: '''{xml -> ""}''' + response: '''{_ -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testRest2', @@ -344,7 +344,7 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - response: '''{xml -> ""}''' + response: '''{_ -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testRest2', @@ -366,14 +366,14 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/test1', port: 9999, - response: '''{xml -> ""}''' + response: '''{_ -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testRest2', path: '/test1', port: 9999, - predicate: '''{xml -> xml.name() == 'request1'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.xml.name() == 'request1'}''', + response: '''{_ -> ""}''', method: AddMockRequestData.Method.PUT )) HttpPut request = new HttpPut('http://localhost:9999/test1') @@ -391,13 +391,13 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/test1', port: 9999, - response: '''{xml -> ""}''' + response: '''{_ -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testRest2', path: '/test1', port: 9999, - response: '''{xml -> ""}''', + response: '''{_ -> ""}''', method: AddMockRequestData.Method.DELETE )) HttpDelete request = new HttpDelete('http://localhost:9999/test1') @@ -414,14 +414,14 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/test1', port: 9999, - response: '''{xml -> ""}''' + response: '''{_ -> ""}''' )) controlServerClient.addMock(new AddMockRequestData( name: 'testRest2', path: '/test1', port: 9999, - predicate: '''{xml -> xml.name() == 'request1'}''', - response: '''{xml -> ""}''', + predicate: '''{req -> req.xml.name() == 'request1'}''', + response: '''{_ -> ""}''', method: AddMockRequestData.Method.PATCH )) HttpPatch request = new HttpPatch('http://localhost:9999/test1') @@ -439,9 +439,9 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - predicate: '''{xml -> xml.name() == 'request'}''', - response: '''{xml -> ""}''', - responseHeaders: '''{ xml -> ['Input-Name':"${xml.name()}"]}''' + predicate: '''{req -> req.xml.name() == 'request'}''', + response: '''{_ -> ""}''', + responseHeaders: '''{ req -> ['Input-Name':"${req.xml.name()}"]}''' )) HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') restPost.entity = new StringEntity('', ContentType.create("text/xml", "UTF-8")) @@ -459,9 +459,9 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - response: '''{xml -> ""}''', - requestHeaders: '''{ hs -> hs['user-agent']?.startsWith('Mozilla') && - hs.pragma == 'no-cache'}''' + predicate: '''{ req -> req.headers['user-agent']?.startsWith('Mozilla') && + req.headers.pragma == 'no-cache'}''', + response: '''{_ -> ""}''' )) HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') restPost.entity = new StringEntity('', ContentType.create("text/xml", "UTF-8")) @@ -488,9 +488,9 @@ class MockServerIntegrationTest extends Specification { name: 'testRest', path: '/testEndpoint', port: 9999, - response: '''{xml -> ""}''', - queryParams: '''{ qp -> qp['q'] == '15' && - qp.id == '1'}''' + predicate: '''{ req -> req.query['q'] == '15' && + req.query.id == '1'}''', + response: '''{_ -> ""}''' )) HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint?q=15&id=1') HttpPost badRestPost = new HttpPost('http://localhost:9999/testEndpoint?q=15&id=2') @@ -506,12 +506,57 @@ class MockServerIntegrationTest extends Specification { restPostResponse.name() == 'goodResponse' } - //TODO def "should dispatch rest mock with post method and parameters"(){} - //TODO def "should dispatch rest mock with post method, response headers and request headers"(){} + def "should add mock that accepts only when request has specific body"() { + given: + controlServerClient.addMock(new AddMockRequestData( + name: 'testRest', + path: '/testEndpoint', + port: 9999, + predicate: '''{req -> req.text == 'hello=world&id=3'}''', + response: '''{_ -> ""}''' + )) + HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') + restPost.entity = new StringEntity('hello=world&id=3', ContentType.create("text/plain", "UTF-8")) + HttpPost badRestPost = new HttpPost('http://localhost:9999/testEndpoint') + badRestPost.entity = new StringEntity('hello=world&id=2', ContentType.create("text/plain", "UTF-8")) + when: + CloseableHttpResponse badResponse = client.execute(badRestPost) + then: + GPathResult badRestPostResponse = Util.extractXmlResponse(badResponse) + badRestPostResponse.name() == 'invalidInput' + when: + CloseableHttpResponse response = client.execute(restPost) + then: + GPathResult restPostResponse = Util.extractXmlResponse(response) + restPostResponse.name() == 'goodResponse' + } + + def "should add mock which response json to json"() { + given: + controlServerClient.addMock(new AddMockRequestData( + name: 'testRest', + path: '/testEndpoint', + port: 9999, + predicate: '''{req -> req.json.id == 1 && req.json.ar == ["a", true]}''', + response: '''{req -> """{"name":"goodResponse-${req.json.id}"}"""}''' + )) + HttpPost restPost = new HttpPost('http://localhost:9999/testEndpoint') + restPost.entity = new StringEntity('{"id":1, "ar":["a", true]}', ContentType.create("text/json", "UTF-8")) + HttpPost badRestPost = new HttpPost('http://localhost:9999/testEndpoint') + badRestPost.entity = new StringEntity('{"id":1, "ar":["a", false]}', ContentType.create("text/json", "UTF-8")) + when: + CloseableHttpResponse badResponse = client.execute(badRestPost) + then: + GPathResult badRestPostResponse = Util.extractXmlResponse(badResponse) + badRestPostResponse.name() == 'invalidInput' + when: + CloseableHttpResponse response = client.execute(restPost) + then: + Object restPostResponse = Util.extractJsonResponse(response) + restPostResponse.name == 'goodResponse-1' + } //TODO def "should get mock report"(){} //TODO def "should get list mocks"(){} //TODO def "should validate mock when creating" - - //TODO def "should handle json input and output"(){} }