From 20ff1762d88a5862503300a97b43d58498722150 Mon Sep 17 00:00:00 2001 From: Aron Roberts Date: Fri, 28 Aug 2009 02:48:59 +0000 Subject: [PATCH] CSPACE-360,CSPACE-384: For client test framework: Restored two inadvertently deleted files. Moved all three files in framework to main tree in client module, from test tree, due to unresolved issues in sharing test code across modules. --- .../services/client/AbstractServiceTest.java | 414 ++++++++++++++++++ .../services/client/ServiceRequestType.java | 209 +++++++++ .../services/client}/ServiceTest.java | 0 3 files changed, 623 insertions(+) create mode 100644 services/client/src/main/java/org/collectionspace/services/client/AbstractServiceTest.java create mode 100644 services/client/src/main/java/org/collectionspace/services/client/ServiceRequestType.java rename services/client/src/{test/java/org/collectionspace/services/client/test => main/java/org/collectionspace/services/client}/ServiceTest.java (100%) diff --git a/services/client/src/main/java/org/collectionspace/services/client/AbstractServiceTest.java b/services/client/src/main/java/org/collectionspace/services/client/AbstractServiceTest.java new file mode 100644 index 000000000..ed763da47 --- /dev/null +++ b/services/client/src/main/java/org/collectionspace/services/client/AbstractServiceTest.java @@ -0,0 +1,414 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * 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. + */ +package org.collectionspace.services.client.test; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.EntityEnclosingMethod; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.commons.httpclient.methods.StringRequestEntity; + +import org.collectionspace.services.client.TestServiceClient; +import org.collectionspace.services.client.test.ServiceRequestType; + +import org.jboss.resteasy.client.ClientResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * AbstractServiceTest, abstract base class for the client tests to be performed + * to test an entity or relation service. + * + * For Javadoc descriptions of this class's methods, see the ServiceTest interface. + */ +public abstract class AbstractServiceTest implements ServiceTest { + + final Logger logger = LoggerFactory.getLogger(AbstractServiceTest.class); + + // An HTTP client, used for performing several negative (failure) tests. + // + // @TODO To be replaced with RESTeasy's ClientRequest (a higher-level API + // that is based on HttpClient), per Issue CSPACE-386. + protected HttpClient httpClient = new HttpClient(); + + // A base-level client, used (only) to obtain the base service URL. + private static final TestServiceClient serviceClient = new TestServiceClient(); + + // A resource identifier believed to be non-existent in actual use, + // used when testing service calls that reference non-existent resources. + protected final String NON_EXISTENT_ID = createNonExistentIdentifier(); + + // The HTTP status code expected to be returned in the response, + // from a request made to a service (where relevant). + int EXPECTED_STATUS_CODE = 0; + + // The generic type of service request being tested (e.g. CREATE, UPDATE, DELETE). + // + // This makes it possible to check behavior specific to that type of request, + // such as the set of valid status codes that may be returned. + ServiceRequestType REQUEST_TYPE = ServiceRequestType.NON_EXISTENT; + + // --------------------------------------------------------------- + // CRUD tests : CREATE tests + // --------------------------------------------------------------- + + // Success outcomes + + @Override + public abstract void create(); + + protected void setupCreate() { + clearSetup(); + // Expected status code: 201 Created + EXPECTED_STATUS_CODE = Response.Status.CREATED.getStatusCode(); + // Type of service request being tested + REQUEST_TYPE = ServiceRequestType.CREATE; + } + + @Override + public abstract void createMultiple(); + + // No setup required for createMultiple() + + // Failure outcomes + + @Override + public void createNull() { + } + + // No setup required for createNull() + + @Override + public abstract void createWithMalformedXml(); + + protected void setupCreateWithMalformedXml() { + clearSetup(); + // Expected status code: 400 Bad Request + EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.CREATE; + } + + @Override + public abstract void createWithWrongXmlSchema(); + + protected void setupCreateWithWrongXmlSchema() { + clearSetup(); + // Expected status code: 400 Bad Request + EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.CREATE; + } + + + // --------------------------------------------------------------- + // CRUD tests : READ tests + // --------------------------------------------------------------- + + // Success outcomes + + @Override + public abstract void read(); + + protected void setupRead() { + clearSetup(); + // Expected status code: 200 OK + EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.READ; + } + + // Failure outcomes + + @Override + public abstract void readNonExistent(); + + protected void setupReadNonExistent() { + clearSetup(); + // Expected status code: 404 Not Found + EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.READ; + } + + + // --------------------------------------------------------------- + // CRUD tests : READ (list, or multiple) tests + // --------------------------------------------------------------- + + // Success outcomes + + @Override + public abstract void readList(); + + protected void setupReadList() { + clearSetup(); + // Expected status code: 200 OK + EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.READ_MULTIPLE; + } + + // Failure outcomes + + // None tested at present. + + + // --------------------------------------------------------------- + // CRUD tests : UPDATE tests + // --------------------------------------------------------------- + + // Success outcomes + // ---------------- + + @Override + public abstract void update(); + + protected void setupUpdate() { + clearSetup(); + // Expected status code: 200 OK + EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.UPDATE; + } + + // Failure outcomes + + @Override + public abstract void updateWithMalformedXml(); + + protected void setupUpdateWithMalformedXml() { + clearSetup(); + // Expected status code: 400 Bad Request + EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.UPDATE; + } + + @Override + public abstract void updateWithWrongXmlSchema(); + + protected void setupUpdateWithWrongXmlSchema() { + clearSetup(); + // Expected status code: 400 Bad Request + EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.UPDATE; + } + + @Override + public abstract void updateNonExistent(); + + protected void setupUpdateNonExistent() { + clearSetup(); + // Expected status code: 404 Not Found + EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.UPDATE; + } + + + // --------------------------------------------------------------- + // CRUD tests : DELETE tests + // --------------------------------------------------------------- + + // Success outcomes + + @Override + public abstract void delete(); + + protected void setupDelete() { + clearSetup(); + // Expected status code: 200 OK + EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.DELETE; + } + + // Failure outcomes + + @Override + public abstract void deleteNonExistent(); + + protected void setupDeleteNonExistent() { + clearSetup(); + // Expected status code: 404 Not Found + EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode(); + REQUEST_TYPE = ServiceRequestType.DELETE; + } + + + // --------------------------------------------------------------- + // Abstract utility methods + // + // Must be implemented by classes that extend + // this abstract base class. + // --------------------------------------------------------------- + + /** + * Returns the URL path component of the service. + * + * This component will follow directly after the + * base path, if any. + */ + protected abstract String getServicePathComponent(); + + + // --------------------------------------------------------------- + // Utility methods + // --------------------------------------------------------------- + + /** + * Reinitializes setup values, to help expose any unintended reuse + * of those values between tests. + */ + protected void clearSetup() { + EXPECTED_STATUS_CODE = 0; + REQUEST_TYPE = ServiceRequestType.NON_EXISTENT; + } + + // @TODO Add Javadoc comments to all methods requiring them, below. + + protected String invalidStatusCodeMessage(ServiceRequestType requestType, int statusCode) { + return + "Status code '" + statusCode + "' in response is NOT within the expected set: " + + requestType.validStatusCodesAsString(); + } + + protected String getServiceRootURL() { + return serviceClient.getBaseURL() + getServicePathComponent(); + } + + protected String getResourceURL(String resourceIdentifier) { + return getServiceRootURL() + "/" + resourceIdentifier; + } + + protected int submitRequest(HttpMethod method) { + int statusCode = 0; + try { + statusCode = httpClient.executeMethod(method); + } catch(HttpException e) { + logger.error("Fatal protocol violation: ", e); + } catch(IOException e) { + logger.error("Fatal transport error: ", e); + } catch(Exception e) { + logger.error("Unknown exception: ", e); + } finally { + // Release the connection. + method.releaseConnection(); + } + return statusCode; + } + + protected int submitRequest(EntityEnclosingMethod method, RequestEntity entity) { + int statusCode = 0; + try { + method.setRequestEntity(entity); + statusCode = httpClient.executeMethod(method); + } catch(HttpException e) { + logger.error("Fatal protocol violation: ", e); + } catch(IOException e) { + logger.error("Fatal transport error: ", e); + } catch(Exception e) { + logger.error("Unknown exception: ", e); + } finally { + // Release the connection. + method.releaseConnection(); + } + return statusCode; + } + + protected StringRequestEntity getXmlEntity(String contents) { + if (contents == null) { + contents = ""; + } + StringRequestEntity entity = null; + final String XML_DECLARATION = + ""; + final String XML_CONTENT_TYPE=MediaType.APPLICATION_XML; + final String UTF8_CHARSET_NAME = "UTF-8"; + try { + entity = + new StringRequestEntity( + XML_DECLARATION + contents, XML_CONTENT_TYPE, UTF8_CHARSET_NAME); + } catch (UnsupportedEncodingException e) { + logger.error("Unsupported character encoding error: ", e); + } + return entity; + } + + protected String extractId(ClientResponse res) { + MultivaluedMap mvm = res.getMetadata(); + String uri = (String) ((ArrayList) mvm.get("Location")).get(0); + verbose("extractId:uri=" + uri); + String[] segments = uri.split("/"); + String id = segments[segments.length - 1]; + verbose("id=" + id); + return id; + } + + protected void verbose(String msg) { + if (logger.isDebugEnabled()) { + logger.debug(msg); + } + } + + protected void verbose(String msg, Object o, Class clazz) { + try{ + verbose(msg); + JAXBContext jc = JAXBContext.newInstance(clazz); + Marshaller m = jc.createMarshaller(); + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, + Boolean.TRUE); + m.marshal(o, System.out); + }catch(Exception e){ + e.printStackTrace(); + } + } + + protected void verboseMap(MultivaluedMap map) { + for(Object entry : map.entrySet()){ + MultivaluedMap.Entry mentry = (MultivaluedMap.Entry) entry; + verbose(" name=" + mentry.getKey() + " value=" + mentry.getValue()); + } + } + + protected String createIdentifier() { + long identifier = System.currentTimeMillis(); + return Long.toString(identifier); + } + + protected String createNonExistentIdentifier() { + return Long.toString(Long.MAX_VALUE); + } + +} + + diff --git a/services/client/src/main/java/org/collectionspace/services/client/ServiceRequestType.java b/services/client/src/main/java/org/collectionspace/services/client/ServiceRequestType.java new file mode 100644 index 000000000..65ec7734a --- /dev/null +++ b/services/client/src/main/java/org/collectionspace/services/client/ServiceRequestType.java @@ -0,0 +1,209 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * 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. + */ + +package org.collectionspace.services.client.test; + +import java.util.Arrays; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ServiceRequestType, identifies types of service requests + * and the valid HTTP status codes that can be returned from + * each type of request. + * + * Used by client tests of services. + * + * $LastChangedRevision: 566 $ + * $LastChangedDate$ + */ +public enum ServiceRequestType { + + // Define each of the service request types and their valid HTTP status codes. + + CREATE { + @Override + public int[] validStatusCodes() { + final int[] STATUS_CODES = { + Response.Status.CREATED.getStatusCode(), + Response.Status.BAD_REQUEST.getStatusCode(), + Response.Status.FORBIDDEN.getStatusCode(), + Response.Status.CONFLICT.getStatusCode(), + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() + }; + Arrays.sort(STATUS_CODES); + return STATUS_CODES; + } + @Override + public boolean isValidStatusCode(int statusCode) { + if (Arrays.binarySearch(CREATE.validStatusCodes(), statusCode) >= 0) { + return true; + } else { + return false; + } + } + @Override + public String validStatusCodesAsString() { + return Arrays.toString(CREATE.validStatusCodes()); + } + }, // Note that commas are required at the end of each enum block, except the last. + + + READ { + @Override + public int[] validStatusCodes() { + final int[] STATUS_CODES = { + Response.Status.OK.getStatusCode(), + Response.Status.FORBIDDEN.getStatusCode(), + Response.Status.NOT_FOUND.getStatusCode(), + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() + }; + Arrays.sort(STATUS_CODES); + return STATUS_CODES; + } + @Override + public boolean isValidStatusCode(int statusCode) { + if (Arrays.binarySearch(READ.validStatusCodes(), statusCode) >= 0) { + return true; + } else { + return false; + } + } + @Override + public String validStatusCodesAsString() { + return Arrays.toString(READ.validStatusCodes()); + } + }, + + + READ_MULTIPLE { + @Override + public int[] validStatusCodes() { + final int[] STATUS_CODES = { + Response.Status.OK.getStatusCode(), + Response.Status.BAD_REQUEST.getStatusCode(), + Response.Status.FORBIDDEN.getStatusCode(), + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() + }; + Arrays.sort(STATUS_CODES); + return STATUS_CODES; + } + @Override + public boolean isValidStatusCode(int statusCode) { + if (Arrays.binarySearch(READ_MULTIPLE.validStatusCodes(), statusCode) >= 0) { + return true; + } else { + return false; + } + } + @Override + public String validStatusCodesAsString() { + return Arrays.toString(READ_MULTIPLE.validStatusCodes()); + } + }, + + + UPDATE { + @Override + public int[] validStatusCodes() { + final int[] STATUS_CODES = { + Response.Status.OK.getStatusCode(), + Response.Status.BAD_REQUEST.getStatusCode(), + Response.Status.FORBIDDEN.getStatusCode(), + Response.Status.NOT_FOUND.getStatusCode(), + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() + }; + Arrays.sort(STATUS_CODES); + return STATUS_CODES; + } + @Override + public boolean isValidStatusCode(int statusCode) { + if (Arrays.binarySearch(UPDATE.validStatusCodes(), statusCode) >= 0) { + return true; + } else { + return false; + } + } + @Override + public String validStatusCodesAsString() { + return Arrays.toString(UPDATE.validStatusCodes()); + } + }, + + + DELETE { + @Override + public int[] validStatusCodes() { + final int[] STATUS_CODES = { + Response.Status.OK.getStatusCode(), + Response.Status.FORBIDDEN.getStatusCode(), + Response.Status.NOT_FOUND.getStatusCode(), + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() + }; + Arrays.sort(STATUS_CODES); + return STATUS_CODES; + } + @Override + public boolean isValidStatusCode(int statusCode) { + if (Arrays.binarySearch(DELETE.validStatusCodes(), statusCode) >= 0) { + return true; + } else { + return false; + } + } + @Override + public String validStatusCodesAsString() { + return Arrays.toString(DELETE.validStatusCodes()); + } + }, + + + // Used by guard code. + NON_EXISTENT { + @Override + public int[] validStatusCodes() { + final int[] STATUS_CODES = { 0 }; + Arrays.sort(STATUS_CODES); + return STATUS_CODES; + } + @Override + public boolean isValidStatusCode(int statusCode) { + return false; + } + @Override + public String validStatusCodesAsString() { + return Arrays.toString(NON_EXISTENT.validStatusCodes()); + } + }; + + // Template methods to be implemented by each ServiceRequestType. + + public abstract int[] validStatusCodes(); + + public abstract boolean isValidStatusCode(int statusCode); + + public abstract String validStatusCodesAsString(); + +} diff --git a/services/client/src/test/java/org/collectionspace/services/client/test/ServiceTest.java b/services/client/src/main/java/org/collectionspace/services/client/ServiceTest.java similarity index 100% rename from services/client/src/test/java/org/collectionspace/services/client/test/ServiceTest.java rename to services/client/src/main/java/org/collectionspace/services/client/ServiceTest.java -- 2.47.3