From 6ecb79fdb721c80da9214eddce074d4d0cbaa04b Mon Sep 17 00:00:00 2001 From: kgunga Date: Mon, 5 Sep 2016 10:08:15 +0300 Subject: [PATCH 1/3] Support namespace validation in SOAP mock APIs generated from a WSDL --- .gitignore | 3 ++ .../mock/service/PropertyProcessor.java | 33 +++++++++++-- .../sf/jaceko/mock/service/WsdlProcessor.java | 12 +++++ .../it/WsdlProcessingIntegrationTest.java | 20 ++++++++ .../PropertyProcessorFileReadingTest.java | 12 +++-- .../mock/service/WsdlProcessorTest.java | 7 ++- .../resources/it/hello-and-schema-check.wsdl | 49 +++++++++++++++++++ src/test/resources/it/ws-mock.properties | 5 ++ 8 files changed, 131 insertions(+), 10 deletions(-) create mode 100755 src/test/resources/it/hello-and-schema-check.wsdl diff --git a/.gitignore b/.gitignore index 2ddd19a..f6a7666 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ target *.iml .idea +.classpath +.project +.settings diff --git a/src/main/java/net/sf/jaceko/mock/service/PropertyProcessor.java b/src/main/java/net/sf/jaceko/mock/service/PropertyProcessor.java index 215218e..0c321ee 100755 --- a/src/main/java/net/sf/jaceko/mock/service/PropertyProcessor.java +++ b/src/main/java/net/sf/jaceko/mock/service/PropertyProcessor.java @@ -81,6 +81,8 @@ public class PropertyProcessor { private static final String BINARY = "BINARY"; private static final String NAMESPACE = "NAMESPACE"; + + private static final String VALIDATE_SCHEMA = "VALIDATE_SCHEMA"; private static final Pattern SERVICE_PATTERN = Pattern.compile("^SERVICE\\[([0-9]+)\\]$"); private static final Pattern OPERATION_PATTERN = Pattern.compile("^OPERATION\\[([0-9]+)\\]$"); @@ -158,9 +160,30 @@ private void setServiceProperties(final WebService service, final String service service.setIgnoreXmlDeclaration(Boolean.valueOf(propertyValue)); } else if (serviceProperty.equals(ENABLE_RESOURCE_PATHS)) { service.setEnableResourcePaths(Boolean.valueOf(propertyValue)); + } else if (serviceProperty.equals(VALIDATE_SCHEMA) && propertyValue.equals("true")) { + StringBuilder operationsNamespaces = getCommaSeparatedWsdlNamespaces(); + + Collection operations = service.getOperations(); + for(WebserviceOperation operation : operations) { + operation.setNameSpaces(operationsNamespaces.toString()); + } } } + private StringBuilder getCommaSeparatedWsdlNamespaces() { + StringBuilder operationsNamespaces = new StringBuilder(); + Map namespaces = wsdlProcessor.getWsdlNamespaces(); + boolean first = true; + for (String ns : namespaces.values()) { + if (!first) { + operationsNamespaces.append(","); + } + operationsNamespaces.append(ns); + first = false; + } + return operationsNamespaces; + } + private WebService getService(final Map services, final int serviceIndex) { WebService service = services.get(serviceIndex); if (service == null) { @@ -207,10 +230,12 @@ private void setDefaultResponseText(final WebserviceOperation operation) { if( operation.isBinary() ) { operation.setDefaultResponseBinaryContent(fileReader.readBinaryFileContents(operation.getDefaultResponseFile())); } else { - final String fileText = fileReader.readFileContents(operation.getDefaultResponseFile()); - if (fileText != null) { - operation.setDefaultResponseText(fileText); - } + if (operation.getDefaultResponseFile() != null) { + final String fileText = fileReader.readFileContents(operation.getDefaultResponseFile()); + if (fileText != null) { + operation.setDefaultResponseText(fileText); + } + } } } diff --git a/src/main/java/net/sf/jaceko/mock/service/WsdlProcessor.java b/src/main/java/net/sf/jaceko/mock/service/WsdlProcessor.java index 4eb99ce..56c09d9 100755 --- a/src/main/java/net/sf/jaceko/mock/service/WsdlProcessor.java +++ b/src/main/java/net/sf/jaceko/mock/service/WsdlProcessor.java @@ -43,6 +43,7 @@ public class WsdlProcessor { private static final Logger LOG = Logger.getLogger(WsdlProcessor.class); private WSDLFactory factory; + private Map namespaces; public WsdlProcessor() { try { @@ -59,6 +60,7 @@ public List getOperationsFromWsdl(final String wsdlFileName WSDLReader wsdlReader = factory.newWSDLReader(); try { Definition def = wsdlReader.readWSDL(null, new InputSource(new StringReader(fileText))); + namespaces = def.getNamespaces(); SoapMessageBuilder soapMessageBuilder = new SoapMessageBuilder(def, fileText); Map bindingsMap = def.getBindings(); Collection bindings = bindingsMap.values(); @@ -101,5 +103,15 @@ public List getOperationsFromWsdl(final String wsdlFileName return mockOperations; } + + /** + * Gets namespaces parsed from given WSDL in getOperationsFromWsdl method. + * getOperationsFromWsdl must first be invoked. + * + * @return Map containing prefixes as keys and namespaces as values + */ + public Map getWsdlNamespaces() { + return namespaces; + } } diff --git a/src/test/java/net/sf/jaceko/mock/it/WsdlProcessingIntegrationTest.java b/src/test/java/net/sf/jaceko/mock/it/WsdlProcessingIntegrationTest.java index 9155a64..ac30252 100755 --- a/src/test/java/net/sf/jaceko/mock/it/WsdlProcessingIntegrationTest.java +++ b/src/test/java/net/sf/jaceko/mock/it/WsdlProcessingIntegrationTest.java @@ -33,6 +33,9 @@ public class WsdlProcessingIntegrationTest { private static final String SERVICES = "http://localhost:8080/mock/services"; private static final String HELLO_MOCK_ENDPOINT = "http://localhost:8080/mock/services/SOAP/hello-soap-withwsdl/endpoint"; + + private static final String HELLO_MOCK_ENDPOINT_SCHEMA_CHECK = "http://localhost:8080/mock/services/SOAP/hello-soap-withwsdl-and-schema-check/endpoint"; + private static final String HELLO_REQUEST = "\r\n" + " \r\n" @@ -41,6 +44,14 @@ public class WsdlProcessingIntegrationTest { + " {0}\r\n" + " \r\n" + " \r\n" + ""; + + private static final String HELLO_REQUEST_INVALID_NS = "\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " {0}\r\n" + + " \r\n" + + " \r\n" + ""; private static final String CONVERSION_RATE_ENDPOINT = "http://localhost:8080/mock/services/SOAP/webservicex-rate-convertor/endpoint"; private static final String CONVERSION_RATE_REQUEST = "\r\n" @@ -104,5 +115,14 @@ public void shouldReturnDefaultHelloResponseGeneratedFromWsdlFile() throws Clien Document serviceResponseDoc = new DocumentImpl(response.getBody()); assertThat(serviceResponseDoc, hasXPath("//Envelope/Body/sayHelloResponse/greeting")); } + + @Test + public void shouldResponseWithUndefinedWebserviceOperationDueToInvalidNamespaceUsingWsdlFile() throws ClientProtocolException, IOException, + ParserConfigurationException, SAXException { + + MockResponse response = requestSender.sendPostRequest(HELLO_MOCK_ENDPOINT_SCHEMA_CHECK, HELLO_REQUEST_INVALID_NS, MediaType.TEXT_XML); + assertThat(response.getCode(), is(HttpStatus.SC_NOT_FOUND)); + + } } diff --git a/src/test/java/net/sf/jaceko/mock/service/PropertyProcessorFileReadingTest.java b/src/test/java/net/sf/jaceko/mock/service/PropertyProcessorFileReadingTest.java index 2824c52..8a90654 100755 --- a/src/test/java/net/sf/jaceko/mock/service/PropertyProcessorFileReadingTest.java +++ b/src/test/java/net/sf/jaceko/mock/service/PropertyProcessorFileReadingTest.java @@ -7,7 +7,10 @@ import net.sf.jaceko.mock.model.webservice.WebService; import net.sf.jaceko.mock.model.webservice.WebserviceOperation; import net.sf.jaceko.mock.util.FileReader; + +import org.hamcrest.core.IsNull; import org.junit.Test; +import org.mockito.internal.matchers.*; import org.w3c.dom.Document; import org.xml.sax.SAXException; @@ -22,7 +25,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.IsEqual.equalTo; -import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.core.IsNull.*; import static org.hamcrest.xml.HasXPath.hasXPath; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -38,7 +41,8 @@ public PropertyProcessorFileReadingTest() { @Test public void shouldReadWsdlContentsFromFile() throws IOException, ParserConfigurationException, SAXException { String propertyString = "SERVICE[0].NAME=ticketing\r\n" + "SERVICE[0].WSDL=hello-for-unit-tests.wsdl\r\n" - + "SERVICE[0].TYPE=SOAP\r\n" + "SERVICE[0].OPERATION[0].INPUT_MESSAGE=someRequest\r\n"; + + "SERVICE[0].TYPE=SOAP\r\n" + "SERVICE[0].OPERATION[0].INPUT_MESSAGE=someRequest\r\n" + + "SERVICE[0].VALIDATE_SCHEMA=true"; Reader reader = new StringReader(propertyString); MockConfigurationHolder configuration = propertyProcessor.process(reader); @@ -47,7 +51,7 @@ public void shouldReadWsdlContentsFromFile() throws IOException, ParserConfigura String wsdlText = soapService.getWsdlText(); Document wsdlDoc = new DocumentImpl(wsdlText); assertThat(wsdlDoc, hasXPath("/definitions/message/@name", equalTo("SayHelloRequest"))); - + assertThat(soapService.getOperations().iterator().next().getNameSpaces(), is(notNullValue())); } @Test @@ -67,7 +71,6 @@ public void shouldReadDefaultResponseContentsFromFile() throws IOException, Pars assertThat(responseDoc, hasXPath("/dummyResponse/reqId", equalTo("789789"))); assertThat(responseDoc, hasXPath("/dummyResponse/status", equalTo("OK"))); - } @Test @@ -82,6 +85,7 @@ public void shouldContinueIfResponseFileNotFound() throws IOException, ParserCon WebService soapService = services.iterator().next(); WebserviceOperation operation = soapService.getOperation(0); assertThat(operation, notNullValue()); + assertThat(operation.getNameSpaces(), nullValue()); } diff --git a/src/test/java/net/sf/jaceko/mock/service/WsdlProcessorTest.java b/src/test/java/net/sf/jaceko/mock/service/WsdlProcessorTest.java index b6c55d5..fc288ee 100755 --- a/src/test/java/net/sf/jaceko/mock/service/WsdlProcessorTest.java +++ b/src/test/java/net/sf/jaceko/mock/service/WsdlProcessorTest.java @@ -4,6 +4,9 @@ import net.sf.jaceko.mock.matcher.OperationHavingNameEqualTo; import net.sf.jaceko.mock.model.webservice.WebserviceOperation; import net.sf.jaceko.mock.util.FileReader; + +import org.hamcrest.core.IsNot; +import org.hamcrest.core.IsNull; import org.junit.Test; import org.w3c.dom.Document; import org.xml.sax.SAXException; @@ -32,7 +35,8 @@ public void shouldFetchOperationsFromWsdlFile_DocumetStyle() { assertThat(operationsFromWsdl, hasItem(new OperationHavingNameEqualTo("lockRequestElement"))); assertThat(operationsFromWsdl, hasItem(new OperationHavingNameEqualTo("unlockRequestElement"))); assertThat(operationsFromWsdl, hasItem(new OperationHavingNameEqualTo("purgeRequestElement"))); - + assertThat(wsdlProcessor.getWsdlNamespaces().size(), is(4)); + } @Test @@ -41,7 +45,6 @@ public void shouldFetchOperationsFromWsdlFile_RPCStyle() { fileReader.readFileContents("hello-for-unit-tests.wsdl")); assertThat(operationsFromWsdl.size(), is(1)); assertThat(operationsFromWsdl, hasItem(new OperationHavingNameEqualTo("sayHello"))); - } @Test diff --git a/src/test/resources/it/hello-and-schema-check.wsdl b/src/test/resources/it/hello-and-schema-check.wsdl new file mode 100755 index 0000000..23dc4cf --- /dev/null +++ b/src/test/resources/it/hello-and-schema-check.wsdl @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Service that says hello + + + + + \ No newline at end of file diff --git a/src/test/resources/it/ws-mock.properties b/src/test/resources/it/ws-mock.properties index 1d9ade4..b689aaa 100755 --- a/src/test/resources/it/ws-mock.properties +++ b/src/test/resources/it/ws-mock.properties @@ -88,3 +88,8 @@ SERVICE[9].OPERATION[0].BINARY=true SERVICE[9].OPERATION[0].INPUT_MESSAGE=dummySoapRequest SERVICE[9].OPERATION[0].DEFAULT_RESPONSE=default_soap_multipart_response.dat SERVICE[9].OPERATION[0].DEFAULT_RESPONSE_CONTENT_TYPE=multipart/related;type="application/xop+xml";start="http://tempuri.org/0";boundary="boundary123123";start-info="application/soap+xml" + +SERVICE[10].NAME=hello-soap-withwsdl-and-schema-check +SERVICE[10].WSDL=hello-schema-check.wsdl +SERVICE[10].TYPE=SOAP +SERVICE[10].VALIDATE_SCHEMA=true \ No newline at end of file From 0450e513a8d050aaa69be356973d2a543b83828e Mon Sep 17 00:00:00 2001 From: kgunga Date: Mon, 5 Sep 2016 10:21:33 +0300 Subject: [PATCH 2/3] Instructions for: Support namespace validation in SOAP mock APIs generated from a WSDL --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d6a107d..947d3f7 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,10 @@ Endpoints are configured in `ws-mock.properties` file under `resources` folder. SERVICE[i].WSDL name of wsdl file, relevant only to SOAP webservices; the wsdl file should be placed in the classpath visible to the mock service application + + SERVICE[i].VALIDATE_SCHEMA + boolean value true to validate, or false (or not present at all) to not validate namespaces of request as defined in WSDL file + SERVICE[i].OPERATION[j].INPUT_MESSAGE (optional) root element of request message; relevant only to SOAP webservices; skip this if WSDL is provided, this information will be read from the WSDL file From 8e010d3ab87f38dba0979ba8bafd7500707483d1 Mon Sep 17 00:00:00 2001 From: kgunga Date: Tue, 13 Sep 2016 08:41:49 +0300 Subject: [PATCH 3/3] Added suppoert for PATCH method --- src/main/java/fi/mystes/http/api/PATCH.java | 15 ++++++++ .../mock/resource/RestEndpointResource.java | 19 ++++++++++ .../resource/RestEndpointResourceTest.java | 36 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 src/main/java/fi/mystes/http/api/PATCH.java diff --git a/src/main/java/fi/mystes/http/api/PATCH.java b/src/main/java/fi/mystes/http/api/PATCH.java new file mode 100644 index 0000000..8da0f08 --- /dev/null +++ b/src/main/java/fi/mystes/http/api/PATCH.java @@ -0,0 +1,15 @@ +package fi.mystes.http.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.ws.rs.HttpMethod; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@HttpMethod("PATCH") +public @interface PATCH { + +} \ No newline at end of file diff --git a/src/main/java/net/sf/jaceko/mock/resource/RestEndpointResource.java b/src/main/java/net/sf/jaceko/mock/resource/RestEndpointResource.java index 10979eb..bfa8235 100755 --- a/src/main/java/net/sf/jaceko/mock/resource/RestEndpointResource.java +++ b/src/main/java/net/sf/jaceko/mock/resource/RestEndpointResource.java @@ -26,6 +26,8 @@ import net.sf.jaceko.mock.service.RequestExecutor; import org.apache.log4j.Logger; +import fi.mystes.http.api.PATCH; + import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; import javax.ws.rs.core.Context; @@ -113,6 +115,23 @@ public Response performPutRequest(@PathParam("serviceName") String serviceName, LOG.debug("serviceName: " + serviceName + ", response:" + mockResponse); return buildWebserviceResponse(mockResponse); } + + @PATCH + @Consumes({"text/*", "application/*"}) + public Response performPatchRequest(@PathParam("serviceName") String serviceName, @Context HttpHeaders headers, String request) { + return performPatchRequest(serviceName, null, headers, request); + } + + @PATCH + @Path("/{postfix: (([^/]+?(/)?)+?)}") + @Consumes({"text/*", "application/*"}) + public Response performPatchRequest(@PathParam("serviceName") String serviceName, @PathParam("postfix") String resourcePath, + @Context HttpHeaders headers, String request) { + validateResourcePath(serviceName, resourcePath); + MockResponse mockResponse = svcLayer.performRequest(serviceName, "PATCH", request, null, resourcePath, headers.getRequestHeaders()); + LOG.debug("serviceName: " + serviceName + ", response:" + mockResponse); + return buildWebserviceResponse(mockResponse); + } @DELETE public Response performDeleteRequest(@PathParam("serviceName") String serviceName, @Context HttpHeaders headers) { diff --git a/src/test/java/net/sf/jaceko/mock/resource/RestEndpointResourceTest.java b/src/test/java/net/sf/jaceko/mock/resource/RestEndpointResourceTest.java index 785318a..6f68850 100755 --- a/src/test/java/net/sf/jaceko/mock/resource/RestEndpointResourceTest.java +++ b/src/test/java/net/sf/jaceko/mock/resource/RestEndpointResourceTest.java @@ -243,6 +243,42 @@ public void shouldReturnPUT_CONFLICTResponse() { assertThat((String) response.getEntity(), is(responseReturnedByServiceLayer)); } + + @Test + public void shouldPerformPatchRequest() { + String request = "def"; + resource.performPatchRequest(SERVICE_NAME, mockHttpHeaders, request); + verify(requestExecutor).performRequest(SERVICE_NAME, "PATCH", request, null, null, headers); + } + + @Test + public void shouldPerformPatchRequestWithHeaders() { + String request = "def"; + + headers.putSingle("someheader", "headervalue"); + headers.putSingle("someotherheader", "anotherheadervalue"); + + resource.performPatchRequest(SERVICE_NAME, mockHttpHeaders, request); + + verify(requestExecutor).performRequest(SERVICE_NAME, "PATCH", request, null, null, headers); + } + + @Test + public void shouldPerformPatchRequestPassingResourceId() { + String resourceId = "resId12"; + String request = "abc"; + resource.performPatchRequest(SERVICE_NAME, resourceId, mockHttpHeaders, request); + verify(requestExecutor).performRequest(SERVICE_NAME, "PATCH", request, null, resourceId, headers); + } + + @Test + public void shouldReturnPATCH_CONFLICTResponse() { + String responseReturnedByServiceLayer = "someResponse123"; + when(requestExecutor.performRequest(anyString(), anyString(), anyString(), anyString(), anyString(), any(MultivaluedMap.class))).thenReturn( + new MockResponse(responseReturnedByServiceLayer, 409)); + Response response = resource.performPatchRequest(SERVICE_NAME, mockHttpHeaders, null); + assertThat((String) response.getEntity(), is(responseReturnedByServiceLayer)); + } @Test public void shouldPerformDeleteRequest() {