2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright © 2009 Regents of the University of California
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.client.test;
26 import java.io.IOException;
27 import java.io.UnsupportedEncodingException;
28 import java.util.ArrayList;
29 import javax.xml.bind.JAXBContext;
30 import javax.xml.bind.Marshaller;
31 import javax.ws.rs.core.MediaType;
32 import javax.ws.rs.core.MultivaluedMap;
33 import javax.ws.rs.core.Response;
35 import org.apache.commons.httpclient.Header;
36 import org.apache.commons.httpclient.HttpClient;
37 import org.apache.commons.httpclient.HttpException;
38 import org.apache.commons.httpclient.HttpMethod;
39 import org.apache.commons.httpclient.HttpStatus;
40 import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
41 import org.apache.commons.httpclient.methods.GetMethod;
42 import org.apache.commons.httpclient.methods.PostMethod;
43 import org.apache.commons.httpclient.methods.PutMethod;
44 import org.apache.commons.httpclient.methods.RequestEntity;
45 import org.apache.commons.httpclient.methods.StringRequestEntity;
47 import org.collectionspace.services.client.TestServiceClient;
48 import org.collectionspace.services.client.test.ServiceRequestType;
50 import org.jboss.resteasy.client.ClientResponse;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
56 * AbstractServiceTest, abstract base class for the client tests to be performed
57 * to test an entity or relation service.
59 * For Javadoc descriptions of this class's methods, see the ServiceTest interface.
61 public abstract class AbstractServiceTest implements ServiceTest {
63 final Logger logger = LoggerFactory.getLogger(AbstractServiceTest.class);
65 // Currently used for performing several negative (failure) tests.
67 // @TODO To be replaced with RESTeasy's ClientRequest, per Issue CSPACE-386.
68 protected HttpClient httpClient = new HttpClient();
70 // Used (only) to obtain the base service URL.
71 private final TestServiceClient serviceClient = new TestServiceClient();
73 // The status code expected to be returned by a test method (where relevant).
74 int EXPECTED_STATUS_CODE = 0;
76 // The generic type of service request being tested (e.g. CREATE, UPDATE, DELETE).
78 // This makes it possible to check behavior specific to that type of request,
79 // such as the set of valid status codes that may be returned.
80 ServiceRequestType REQUEST_TYPE = ServiceRequestType.NON_EXISTENT;
83 // ---------------------------------------------------------------
84 // CRUD tests : CREATE tests
85 // ---------------------------------------------------------------
90 public abstract void create();
92 protected void setupCreate() {
94 // Expected status code: 201 Created
95 EXPECTED_STATUS_CODE = Response.Status.CREATED.getStatusCode();
96 // Type of service request being tested
97 REQUEST_TYPE = ServiceRequestType.CREATE;
101 public abstract void createMultiple();
103 // No setup required for createMultiple()
108 public void createNull() {
111 // No setup required for createNull()
114 public abstract void createWithMalformedXml();
116 protected void setupCreateWithMalformedXml() {
118 // Expected status code: 400 Bad Request
119 EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
120 REQUEST_TYPE = ServiceRequestType.CREATE;
124 public abstract void createWithWrongXmlSchema();
126 protected void setupCreateWithWrongXmlSchema() {
128 // Expected status code: 400 Bad Request
129 EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
130 REQUEST_TYPE = ServiceRequestType.CREATE;
134 // ---------------------------------------------------------------
135 // CRUD tests : READ tests
136 // ---------------------------------------------------------------
141 public abstract void read();
143 protected void setupRead() {
145 // Expected status code: 200 OK
146 EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
147 REQUEST_TYPE = ServiceRequestType.READ;
153 public abstract void readNonExistent();
155 protected void setupReadNonExistent() {
157 // Expected status code: 404 Not Found
158 EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
159 REQUEST_TYPE = ServiceRequestType.READ;
163 // ---------------------------------------------------------------
164 // CRUD tests : READ (list, or multiple) tests
165 // ---------------------------------------------------------------
170 public abstract void readList();
172 protected void setupReadList() {
174 // Expected status code: 200 OK
175 EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
176 REQUEST_TYPE = ServiceRequestType.READ_MULTIPLE;
181 // None tested at present.
184 // ---------------------------------------------------------------
185 // CRUD tests : UPDATE tests
186 // ---------------------------------------------------------------
192 public abstract void update();
194 protected void setupUpdate() {
196 // Expected status code: 200 OK
197 EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
198 REQUEST_TYPE = ServiceRequestType.UPDATE;
204 public abstract void updateWithMalformedXml();
206 protected void setupUpdateWithMalformedXml() {
208 // Expected status code: 400 Bad Request
209 EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
210 REQUEST_TYPE = ServiceRequestType.UPDATE;
214 public abstract void updateWithWrongXmlSchema();
216 protected void setupUpdateWithWrongXmlSchema() {
218 // Expected status code: 400 Bad Request
219 EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
220 REQUEST_TYPE = ServiceRequestType.UPDATE;
224 public abstract void updateNonExistent();
226 protected void setupUpdateNonExistent() {
228 // Expected status code: 404 Not Found
229 EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
230 REQUEST_TYPE = ServiceRequestType.UPDATE;
234 // ---------------------------------------------------------------
235 // CRUD tests : DELETE tests
236 // ---------------------------------------------------------------
241 public abstract void delete();
243 protected void setupDelete() {
245 // Expected status code: 200 OK
246 EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
247 REQUEST_TYPE = ServiceRequestType.DELETE;
253 public abstract void deleteNonExistent();
255 protected void setupDeleteNonExistent() {
257 // Expected status code: 404 Not Found
258 EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
259 REQUEST_TYPE = ServiceRequestType.DELETE;
263 // ---------------------------------------------------------------
264 // Abstract utility methods
266 // Must be implemented by classes that extend
267 // this abstract base class.
268 // ---------------------------------------------------------------
271 * Returns the URL path component of the service.
273 * This component will follow directly after the
276 protected abstract String getServicePathComponent();
279 // ---------------------------------------------------------------
281 // ---------------------------------------------------------------
284 * Reinitializes setup values, to help expose any unintended reuse
285 * of those values between tests.
287 protected void clearSetup() {
288 EXPECTED_STATUS_CODE = 0;
289 REQUEST_TYPE = ServiceRequestType.NON_EXISTENT;
292 // @TODO Add Javadoc comments to all methods requiring them, below.
294 protected String invalidStatusCodeMessage(ServiceRequestType requestType, int statusCode) {
296 "Status code '" + statusCode + "' in response is NOT within the expected set: " +
297 requestType.validStatusCodesAsString();
300 protected String getServiceRootURL() {
301 return serviceClient.getBaseURL() + getServicePathComponent();
304 protected String getResourceURL(String resourceIdentifier) {
305 return getServiceRootURL() + "/" + resourceIdentifier;
308 protected int submitRequest(HttpMethod method) {
311 statusCode = httpClient.executeMethod(method);
312 } catch(HttpException e) {
313 logger.error("Fatal protocol violation: ", e);
314 } catch(IOException e) {
315 logger.error("Fatal transport error: ", e);
316 } catch(Exception e) {
317 logger.error("Unknown exception: ", e);
319 // Release the connection.
320 method.releaseConnection();
325 protected int submitRequest(EntityEnclosingMethod method, RequestEntity entity) {
328 method.setRequestEntity(entity);
329 statusCode = httpClient.executeMethod(method);
330 } catch(HttpException e) {
331 logger.error("Fatal protocol violation: ", e);
332 } catch(IOException e) {
333 logger.error("Fatal transport error: ", e);
334 } catch(Exception e) {
335 logger.error("Unknown exception: ", e);
337 // Release the connection.
338 method.releaseConnection();
343 protected StringRequestEntity getXmlEntity(String contents) {
344 if (contents == null) {
347 StringRequestEntity entity = null;
348 final String XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
349 final String XML_CONTENT_TYPE=MediaType.APPLICATION_XML;
350 final String UTF8_CHARSET_NAME = "UTF-8";
353 new StringRequestEntity(XML_DECLARATION + contents, XML_CONTENT_TYPE, UTF8_CHARSET_NAME);
354 } catch (UnsupportedEncodingException e) {
355 logger.error("Unsupported character encoding error: ", e);
360 protected String extractId(ClientResponse<Response> res) {
361 MultivaluedMap mvm = res.getMetadata();
362 String uri = (String) ((ArrayList) mvm.get("Location")).get(0);
363 verbose("extractId:uri=" + uri);
364 String[] segments = uri.split("/");
365 String id = segments[segments.length - 1];
370 protected void verbose(String msg) {
371 if (logger.isDebugEnabled()) {
376 protected void verbose(String msg, Object o, Class clazz) {
379 JAXBContext jc = JAXBContext.newInstance(clazz);
380 Marshaller m = jc.createMarshaller();
381 m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
383 m.marshal(o, System.out);
389 protected void verboseMap(MultivaluedMap map) {
390 for(Object entry : map.entrySet()){
391 MultivaluedMap.Entry mentry = (MultivaluedMap.Entry) entry;
392 verbose(" name=" + mentry.getKey() + " value=" + mentry.getValue());
396 protected String createIdentifier() {
397 long identifier = System.currentTimeMillis();
398 return Long.toString(identifier);
401 protected String createNonExistentIdentifier() {
402 return Long.toString(Long.MAX_VALUE);