]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
3e5f82381579f8208423df83fbb26d023753f67e
[tmp/jakarta-migration.git] /
1 /**
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:
5  *
6  * http://www.collectionspace.org
7  * http://wiki.collectionspace.org
8  *
9  * Copyright © 2009 Regents of the University of California
10  *
11  * Licensed under the Educational Community License (ECL), Version 2.0.
12  * You may not use this file except in compliance with this License.
13  *
14  * You may obtain a copy of the ECL 2.0 License at
15  * https://source.collectionspace.org/collection-space/LICENSE.txt
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23  
24 package org.collectionspace.services.client.test;
25
26 import java.util.ArrayList;
27 import java.util.List;
28 import javax.ws.rs.core.MultivaluedMap;
29 import javax.ws.rs.core.Response;
30 import javax.xml.bind.JAXBContext;
31 import javax.xml.bind.Marshaller;
32 import org.jboss.resteasy.client.ClientResponse;
33 import org.testng.Assert;
34 import org.testng.annotations.Test;
35
36 import org.collectionspace.services.client.CollectionObjectClient;
37 import org.collectionspace.services.client.test.ServiceRequestType;
38 import org.collectionspace.services.collectionobject.CollectionObject;
39 import org.collectionspace.services.collectionobject.CollectionObjectList;
40
41 import java.io.IOException;
42 import java.io.UnsupportedEncodingException;
43 import java.util.Arrays;
44 import java.util.Set;
45 import javax.ws.rs.core.MediaType;
46 import javax.ws.rs.core.Response.Status;
47 // import org.jboss.resteasy.client.ClientRequest;
48 import org.collectionspace.services.client.TestServiceClient;
49 import org.apache.commons.httpclient.Header;
50 import org.apache.commons.httpclient.HttpClient;
51 import org.apache.commons.httpclient.HttpException;
52 import org.apache.commons.httpclient.HttpMethod;
53 import org.apache.commons.httpclient.HttpStatus;
54 import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
55 import org.apache.commons.httpclient.methods.GetMethod;
56 import org.apache.commons.httpclient.methods.PostMethod;
57 import org.apache.commons.httpclient.methods.PutMethod;
58 import org.apache.commons.httpclient.methods.RequestEntity;
59 import org.apache.commons.httpclient.methods.StringRequestEntity;
60
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 /**
65  * CollectionObjectServiceTest, carries out tests against a
66  * deployed and running CollectionObject Service.
67  * 
68  * $LastChangedRevision$
69  * $LastChangedDate$
70  */
71 public class CollectionObjectServiceTest {
72
73   // Instance variables specific to this test.
74   final Logger logger = LoggerFactory.getLogger(CollectionObjectServiceTest.class);
75   private CollectionObjectClient client = new CollectionObjectClient();
76
77   // Instance variables common to all entity service test classes.
78   private String knownObjectId = null;
79   private final String NON_EXISTENT_ID = createNonExistentIdentifier();
80   private HttpClient httpClient = new HttpClient();
81   private TestServiceClient serviceClient = new TestServiceClient();
82  
83   // ---------------------------------------------------------------
84   // Service Discovery tests
85   // ---------------------------------------------------------------
86
87   // TBA
88   
89   
90   // ---------------------------------------------------------------
91   // CRUD tests : CREATE tests
92   // ---------------------------------------------------------------
93
94   // Success outcomes
95   // ----------------
96   
97   /**
98    * Tests creation of a new resource of the specified type.
99    *
100    * The 'Location' header will contain the URL for the newly created object.
101    * This is required by the extractId() utility method, below.
102    *
103    * The newly-created resource is also used by other test(s)
104    * (e.g. update, delete) which follow, below.
105    */
106   @Test
107   public void create() {
108
109      // Expected status code: 201 Created
110     final int EXPECTED_STATUS_CODE = Response.Status.CREATED.getStatusCode();
111
112     // Type of service request being tested
113     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.CREATE;
114
115     // Submit the request to the service and store the response.
116     String identifier = this.createIdentifier();
117     CollectionObject collectionObject = createCollectionObject(identifier);
118     ClientResponse<Response> res = client.createCollectionObject(collectionObject);
119     int statusCode = res.getStatus();
120
121     // Check the status code of the response: does it match the expected response(s)?
122     verbose("create: status = " + statusCode);
123     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
124       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
125     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
126
127     // Store the ID returned from this create operation for additional tests below.
128     knownObjectId = extractId(res);
129   }
130
131   /**
132    * Creates two or more new objects of the specified type.
133    *
134    * Repeatedly calls the create test, above, and relies on its
135    * test assertions.
136    *
137    * The newly-created objects are also used by other test(s)
138    * (e.g. read multiple/list) which follow, below.
139    */
140   @Test(dependsOnMethods = {"create"})
141   public void createMultiple() {
142     for(int i = 0; i < 3; i++){
143       this.create();
144     }
145   }
146
147   // Failure outcomes
148   // ----------------
149
150   /**
151    * Tests creation of a resource of the specified type by sending a null to the client proxy.
152    *
153    */
154   @Test(dependsOnMethods = {"create"}, expectedExceptions = IllegalArgumentException.class)
155   public void createNull() {
156
157    // Expected result: IllegalArgumentException
158     ClientResponse<Response> res = client.createCollectionObject(null);
159   }
160   
161   /**
162    * Tests creation of a resource of the specified type by sending malformed XML data
163    * in the entity body of the request.
164    */
165 /*
166   @Test(dependsOnMethods = {"create", "testSubmitRequest"})
167   public void createWithMalformedXml() {
168
169     // Expected status code: 400 Bad Request
170     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
171
172     // Type of service request being tested
173     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.CREATE;
174
175     // @TODO This test is currently commented out, because it returns a
176     // 500 Internal Server Error status code, rather than the expected status code.
177
178     // Submit the request to the service and store the response.
179     String url = getServiceRootURL();
180     PostMethod method = new PostMethod(url);
181     final String MALFORMED_XML_DATA =
182       "<malformed_xml>wrong schema contents</malformed_xml"; // Note: intentionally missing bracket.
183     StringRequestEntity entity = getXmlEntity(MALFORMED_XML_DATA);
184     int statusCode = submitRequest(method, entity);
185     
186     // Check the status code of the response: does it match the expected response(s)?
187     verbose("createWithMalformedXml url=" + url + " status=" + statusCode);
188     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
189       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
190     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
191   }
192 */
193
194   /**
195    * Tests creation of a resource of the specified type by sending data
196    * in the wrong schema (e.g. in a format that doesn't match the object's schema)
197    * in the entity body of the request.
198     */
199 /*
200   @Test(dependsOnMethods = {"create", "testSubmitRequest", "createWithMalformedXml"})
201   public void createWithWrongSchema() {
202   
203     // Expected status code: 400 Bad Request
204     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
205
206     // Type of service request being tested
207     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.CREATE;
208
209     // @TODO This test is currently commented out, because it returns a
210     // 500 Internal Server Error status code, rather than the expected status code.
211    
212     // Submit the request to the service and store the response.
213     String url = getServiceRootURL();
214     PostMethod method = new PostMethod(url);
215     final String WRONG_SCHEMA_DATA = "<wrong_schema>wrong schema contents</wrong_schema>";
216     StringRequestEntity entity = getXmlEntity(WRONG_SCHEMA_DATA);
217     int statusCode = submitRequest(method, entity);
218     
219     // Check the status code of the response: does it match the expected response(s)?
220     verbose("createWithWrongSchema url=" + url + " status=" + statusCode);
221     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
222       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
223     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
224   }
225 */  
226
227   /**
228    * Tests creation of a resource of the specified type,
229    * by a user who is not authorized to perform this action.
230    */
231 /*
232   @Test(dependsOnMethods = {"create"})
233   public void createWithoutAuthorization() {
234
235     // Expected status code: 403 Forbidden
236     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
237     
238     // @TODO Currently only a stub.  This test can be implemented
239     // when the service is revised to require authorization. 
240   }
241 */
242
243   /**
244    * Tests creation of a duplicate object of the specified type,
245    * whose unique resource identifier duplicates that of an existing object.
246    */
247 /*
248   @Test(dependsOnMethods = {"create"})
249   public void createDuplicate() {
250
251     // Expected status code: 409 Conflict
252     final int EXPECTED_STATUS_CODE = Response.Status.CONFLICT.getStatusCode();
253
254     // Type of service request being tested
255     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.CREATE;
256     
257     // @TODO This test is currently commented out because our current
258     // services do not appear to permit creation of duplicate records.
259     // Please see below for more details.
260     
261     // Note: there doesn't appear to be a way to create a duplicate
262     // resource (object) by POSTing:
263     //    
264     // 1. We can't POST to a specific resource by ID; that returns a
265     // response with a 405 Method Not Allowed status code.
266     //
267     // 2. If we POST to the container in which new resources are created,
268     // it doesn't appear that we can specify the CSID that the newly-created
269     // resource (object) will receive.
270     //
271     // If the two points above are accurate, this test is thus unneeded, until
272     // and unless, in our service(s), we begin detecting duplicates via a
273     // technique that isn't dependent on CSIDs; for instance, by checking for
274     // duplicate data in other information units (fields) whose values must be unique.
275     // 
276     // One possible example of the above: checking for duplicate Accession numbers
277     // in the "Object entry" information unit in CollectionObject resources.
278   }
279 */
280
281   // ---------------------------------------------------------------
282   // CRUD tests : READ tests
283   // ---------------------------------------------------------------
284
285   // Success outcomes
286   // ----------------
287   
288   /**
289    * Tests reading (i.e. retrieval) of a resource of the specified type.
290    */
291   @Test(dependsOnMethods = {"create"})
292   public void read() {
293
294     // Expected status code: 200 OK
295     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
296
297     // Type of service request being tested
298     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.READ;
299
300     // Submit the request to the service and store the response.
301     ClientResponse<CollectionObject> res = 
302       client.getCollectionObject(knownObjectId);
303     int statusCode = res.getStatus();
304       
305     // Check the status code of the response: does it match the expected response(s)?
306     verbose("read: status = " + statusCode);
307     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
308       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
309     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
310   }
311
312   // Failure outcomes
313   // ----------------
314
315   /**
316    * Tests reading (i.e. retrieval) of a resource of the specified type by a user who
317    * is not authorized to perform this action.
318    */
319 /*
320   @Test(dependsOnMethods = {"read"})
321   public void readWithoutAuthorization() {
322
323     // Expected status code: 403 Forbidden
324     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
325
326     // Type of service request being tested
327     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.READ;
328     
329     // @TODO Currently only a stub.  This test can be implemented
330     // when the service is revised to require authorization. 
331   }
332 */
333
334   /**
335    * Tests reading (i.e. retrieval) of a non-existent object of the specified type,
336    * whose resource identifier does not exist at the specified URL.
337    */
338   @Test(dependsOnMethods = {"read"})
339   public void readNonExistent() {
340
341     // Expected status code: 404 Not Found
342     final int EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
343
344     // Type of service request being tested
345     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.READ;
346
347     // Submit the request to the service and store the response.
348     ClientResponse<CollectionObject> res = 
349       client.getCollectionObject(NON_EXISTENT_ID);
350     int statusCode = res.getStatus();
351
352     // Check the status code of the response: does it match the expected response(s)?
353     verbose("readNonExistent: status = " + res.getStatus());
354     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
355       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
356     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
357   }
358
359
360   // ---------------------------------------------------------------
361   // CRUD tests : READ (list, or multiple) tests
362   // ---------------------------------------------------------------
363
364   // Success outcomes
365   // ----------------
366
367   /**
368    * Tests reading (i.e. retrieval) of a list of multiple objects of the specified type.
369    *
370    * Also expected: The entity body in the response contains
371    * a representation of a list of objects of the specified type.
372    */
373   @Test(dependsOnMethods = {"createMultiple"})
374   public void readList() {
375
376     // Expected status code: 200 OK
377     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
378
379     // Type of service request being tested
380     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.READ_MULTIPLE;
381   
382     // Submit the request to the service and store the response.
383     ClientResponse<CollectionObjectList> res = client.getCollectionObjectList();
384     CollectionObjectList coList = res.getEntity();
385     int statusCode = res.getStatus();
386
387     // Check the status code of the response: does it match the expected response(s)?
388     verbose("readList: status = " + res.getStatus());
389     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
390       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
391     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
392
393     // Optionally output additional data about list members for debugging.
394     boolean iterateThroughList = false;
395     if (iterateThroughList && logger.isDebugEnabled()) {
396       List<CollectionObjectList.CollectionObjectListItem> coItemList =
397         coList.getCollectionObjectListItem();
398       int i = 0;
399       for(CollectionObjectList.CollectionObjectListItem pli : coItemList){
400         verbose("readList: list-item[" + i + "] csid=" + pli.getCsid());
401         verbose("readList: list-item[" + i + "] objectNumber=" + pli.getObjectNumber());
402         verbose("readList: list-item[" + i + "] URI=" + pli.getUri());
403         i++;
404       }
405     }
406     
407   }
408
409   /**
410    * Tests reading (i.e. retrieval) of a list of multiple objects of the specified type
411    * when the contents of the list are expected to be empty.
412    *
413    * Also expected: The entity body in the response contains
414    * a representation of an empty list of objects of the specified type.
415    */
416 /*
417   @Test(dependsOnMethods = {"readList"})
418   public void readEmptyList() {
419
420     // Expected status code: 200 OK
421     // (NOTE: *not* 204 No Content)
422     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
423
424     // Type of service request being tested
425     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.READ_MULTIPLE;
426
427     // @TODO Currently only a stub.  Consider how to implement this.
428   }
429 */
430   
431   // Failure outcomes
432   // ----------------
433
434   /**
435    * Tests reading (i.e. retrieval) of a list of objects of the specified type
436    * when sending unrecognized query parameters with the request.
437    */
438 /*
439   @Test(dependsOnMethods = {"readList"})
440   public void readListWithBadParams() {
441
442     // Expected status code: 400 Bad Request
443     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
444
445     // Type of service request being tested
446     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.READ_MULTIPLE;
447
448     // @TODO This test is currently commented out, because it returns a
449     // 200 OK status code, rather than the expected status code.
450    
451     // @TODO Another variant of this test should use a URL for the service
452     // root that ends in a trailing slash.
453
454     // Submit the request to the service and store the response.
455     String url = getServiceRootURL() + "?param=nonexistent";
456     GetMethod method = new GetMethod(url);
457     int statusCode = submitRequest(method);
458     
459     // Check the status code of the response: does it match the expected response(s)?
460     verbose("readListWithBadParams: url=" + url + " status=" + statusCode);
461     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
462       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
463     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
464   }
465 */
466
467   /**
468    * Tests reading (i.e. retrieval) of a list of objects of the specified type by a user who
469    * is not authorized to perform this action.
470    *
471    */
472 /*
473   @Test(dependsOnMethods = {"readList"})
474   public void readListWithoutAuthorization() {
475
476     // Expected status code: 403 Forbidden
477     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
478
479     // Type of service request being tested
480     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.READ_MULTIPLE;
481
482     // @TODO Currently only a stub.  This test can be implemented
483     // when the service is revised to require authorization. 
484   }
485 */
486  
487
488   // ---------------------------------------------------------------
489   // CRUD tests : UPDATE tests
490   // ---------------------------------------------------------------
491
492   // Success outcomes
493   // ----------------
494
495   /**
496    * Tests updating the content of a resource of the specified type.
497    *
498    * Also expected: The entity body in the response contains
499    * a representation of the updated object of the specified type.
500    */
501   @Test(dependsOnMethods = {"create"})
502   public void update() {
503
504     // Expected status code: 200 OK
505     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
506
507     // Type of service request being tested
508     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.UPDATE;
509
510     // Retrieve an existing resource that we can update.
511     ClientResponse<CollectionObject> res = 
512       client.getCollectionObject(knownObjectId);
513     verbose("read: status = " + res.getStatus());
514     Assert.assertEquals(res.getStatus(), EXPECTED_STATUS_CODE);
515     CollectionObject collectionObject = res.getEntity();
516     verbose("Got object to update with ID: " + knownObjectId,
517         collectionObject, CollectionObject.class);
518
519     // Update the content of this resource.
520     //collectionObject.setCsid("updated-" + knownObjectId);
521     collectionObject.setObjectNumber("updated-" + collectionObject.getObjectNumber());
522     collectionObject.setObjectName("updated-" + collectionObject.getObjectName());
523
524     // Submit the request to the service and store the response.
525     res = client.updateCollectionObject(knownObjectId, collectionObject);
526     int statusCode = res.getStatus();
527     CollectionObject updatedCollectionObject = res.getEntity();
528
529     // Check the status code of the response: does it match the expected response(s)?
530     verbose("update: status = " + res.getStatus());
531     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
532       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
533     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
534     
535     // Check the contents of the response: does it match what was submitted?
536     verbose("update: ", updatedCollectionObject, CollectionObject.class);
537     Assert.assertEquals(updatedCollectionObject.getObjectName(), 
538       collectionObject.getObjectName(), "Data in updated object did not match submitted data.");
539   }
540
541   /**
542    * Tests updating the content of a resource of the specified type
543    * by sending malformed XML data in the entity body of the request.
544    */
545 /*
546   @Test(dependsOnMethods = {"create", "testSubmitRequest"})
547   public void updateWithMalformedXml() {
548
549     // Expected status code: 400 Bad Request
550     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
551
552     // Type of service request being tested
553     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.UPDATE;
554
555     // @TODO This test is currently commented out, because it returns a
556     // 500 Internal Server Error status code, rather than the expected status code.
557
558     // Submit the request to the service and store the response.
559     String url = getResourceURL(knownObjectId);
560     PutMethod method = new PutMethod(url);
561     final String MALFORMED_XML_DATA =
562       "<malformed_xml>wrong schema contents</malformed_xml"; // Note: intentionally missing bracket.
563     StringRequestEntity entity = getXmlEntity(MALFORMED_XML_DATA);
564     int statusCode = submitRequest(method, entity);
565     
566     // Check the status code of the response: does it match the expected response(s)?
567     verbose("updateWithMalformedXml: url=" + url + " status=" + statusCode);
568     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
569       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
570     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
571   }
572 */
573
574   /**
575    * Tests updating the content of a resource of the specified type
576    * by sending data in the wrong schema (e.g. in a format that
577    * doesn't match the object's schema) in the entity body of the request.
578    */
579 /*
580   @Test(dependsOnMethods = {"create", "testSubmitRequest", "createWithMalformedXml"})
581   public void updateWithWrongSchema() {
582   
583     // Expected status code: 400 Bad Request
584     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
585
586     // Type of service request being tested
587     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.UPDATE;
588     
589     // @TODO This test is currently commented out, because it returns a
590     // 500 Internal Server Error status code, rather than the expected status code.
591
592     // Submit the request to the service and store the response.
593     String url = getResourceURL(knownObjectId);
594     PutMethod method = new PutMethod(url);
595     final String WRONG_SCHEMA_DATA = "<wrong_schema>wrong schema contents</wrong_schema>";
596     StringRequestEntity entity = getXmlEntity(WRONG_SCHEMA_DATA);
597     int statusCode = submitRequest(method, entity);
598     
599     // Check the status code of the response: does it match the expected response(s)?
600     verbose("updateWithWrongSchema: url=" + url + " status=" + statusCode);
601     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
602       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
603     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
604   }
605 */
606
607   /**
608    * Tests updating the content of a resource of the specified type,
609    * by a user who is not authorized to perform this action.
610    */
611 /*
612   @Test(dependsOnMethods = {"update"})
613   public void updateWithoutAuthorization() {
614
615     // Expected status code: 403 Forbidden
616     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
617
618     // @TODO Currently only a stub.  This test can be implemented
619     // when the service is revised to require authorization. 
620   }
621 */
622
623   /**
624    * Tests updating the content of a non-existent object of the specified type,
625    * whose resource identifier does not exist.
626    */
627   @Test(dependsOnMethods = {"update"})
628   public void updateNonExistent() {
629
630     // Expected status code: 404 Not Found
631     final int EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
632
633     // Type of service request being tested
634     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.UPDATE;
635
636     // Submit the request to the service and store the response.
637     // Note: The ID used in this 'create' call may be arbitrary.
638     // The only relevant ID may be the one used in updateCollectionObject(), below.
639     CollectionObject collectionObject = createCollectionObject(NON_EXISTENT_ID);
640     ClientResponse<CollectionObject> res =
641       client.updateCollectionObject(NON_EXISTENT_ID, collectionObject);
642     int statusCode = res.getStatus();
643
644     // Check the status code of the response: does it match the expected response(s)?
645     verbose("updateNonExistent: status = " + res.getStatus());
646     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
647       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
648     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
649   }
650
651
652   // ---------------------------------------------------------------
653   // CRUD tests : DELETE tests
654   // ---------------------------------------------------------------
655
656   // Success outcomes
657   // ----------------
658
659   /**
660    * Tests deleting an object of the specified type.
661    *
662    * Expected status code: 200 OK
663    */
664   @Test(dependsOnMethods = 
665     {"create", "read", "testSubmitRequest", "update"})
666   public void delete() {
667
668     // Expected status code: 200 OK
669     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
670
671     // Type of service request being tested
672     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.DELETE;
673
674     // Submit the request to the service and store the response.
675     ClientResponse<Response> res = client.deleteCollectionObject(knownObjectId);
676     int statusCode = res.getStatus();
677
678     // Check the status code of the response: does it match the expected response(s)?
679     verbose("delete: status = " + res.getStatus());
680     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
681       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
682     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
683   }
684
685   // Failure outcomes
686   // ----------------
687
688   /**
689    * Tests deleting an object of the specified type,
690    * by a user who is not authorized to perform this action.
691    */
692 /*
693   @Test(dependsOnMethods = {"delete"})
694   public void deleteWithoutAuthorization() {
695
696     // Expected status code: 403 Forbidden
697     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
698
699     // Type of service request being tested
700     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.DELETE;
701
702     // @TODO Currently only a stub.  This test can be implemented
703     // when the service is revised to require authorization. 
704   }
705 */
706
707   /**
708    * Tests deleting a non-existent object of the specified type,
709    * whose resource identifier does not exist at the specified URL.
710    */
711   @Test(dependsOnMethods = {"delete"})
712   public void deleteNonExistent() {
713
714     // Expected status code: 404 Not Found
715     final int EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
716
717     // Type of service request being tested
718     final ServiceRequestType REQUEST_TYPE = ServiceRequestType.DELETE;
719
720     // Submit the request to the service and store the response.
721     ClientResponse<Response> res =
722       client.deleteCollectionObject(NON_EXISTENT_ID);
723     int statusCode = res.getStatus();
724
725     // Check the status code of the response: does it match the expected response(s)?
726     verbose("deleteNonExistent: status = " + res.getStatus());
727     Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
728       invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
729     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
730   }
731
732
733   // ---------------------------------------------------------------
734   // Utility tests : tests of code used in tests above
735   // ---------------------------------------------------------------
736
737   /**
738    * Tests the HttpClient-based code used to submit data, in various methods below.
739    */
740   @Test(dependsOnMethods = {"create", "read"})
741   public void testSubmitRequest() {
742
743     // Expected status code: 200 OK
744     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
745
746     // Submit the request to the service and store the response.
747     String url = getResourceURL(knownObjectId);
748     GetMethod method = new GetMethod(url);
749     int statusCode = submitRequest(method);
750     
751     // Check the status code of the response: does it match the expected response(s)?
752     verbose("testSubmitRequest: url=" + url + " status=" + statusCode);
753     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
754
755   }
756                 
757
758   // ---------------------------------------------------------------
759   // Utility methods used by tests above
760   // ---------------------------------------------------------------
761   
762   // @TODO Add Javadoc comments to all of these methods.
763
764   // -----------------------------
765   // Methods specific to this test
766   // -----------------------------
767
768   private CollectionObject createCollectionObject(String identifier) {
769     CollectionObject collectionObject = createCollectionObject("objectNumber-" + identifier,
770         "objectName-" + identifier);
771     return collectionObject;
772   }
773
774   private CollectionObject createCollectionObject(String objectNumber, String objectName) {
775     CollectionObject collectionObject = new CollectionObject();
776     collectionObject.setObjectNumber(objectNumber);
777     collectionObject.setObjectName(objectName);
778     return collectionObject;
779   }
780   
781   private String getServicePathComponent() {
782     // @TODO Determine if it is possible to obtain this value programmatically.
783     // We set this in an annotation in the CollectionObjectProxy interface, for instance.
784     final String SERVICE_PATH_COMPONENT = "collectionobjects";
785     return SERVICE_PATH_COMPONENT;
786   }
787   
788   // -------------------------------------------------------------
789   // Methods common to all entity service test classes.
790   //
791   // These can be moved out of individual service test classes
792   // into a common class, perhaps at the top-level 'client' module.
793   // -------------------------------------------------------------
794
795   protected String invalidStatusCodeMessage(ServiceRequestType requestType, int statusCode) {
796     return 
797       "Status code '" + statusCode + "' in response is NOT within the expected set: " +
798       requestType.validStatusCodesAsString();
799   }
800   
801   private String getServiceRootURL() {
802     return serviceClient.getBaseURL() + getServicePathComponent();
803   }
804
805   private String getResourceURL(String resourceIdentifier) {
806     return getServiceRootURL() + "/" + resourceIdentifier;
807   }
808
809   private int submitRequest(HttpMethod method) {
810    int statusCode = 0;
811     try {
812       statusCode = httpClient.executeMethod(method);
813     } catch(HttpException e) {
814       logger.error("Fatal protocol violation: ", e);
815     } catch(IOException e) {
816       logger.error("Fatal transport error: ", e);
817     } catch(Exception e) {
818       logger.error("Unknown exception: ", e);
819     } finally {
820       // Release the connection.
821       method.releaseConnection();
822     }
823     return statusCode;
824   }
825   
826   private int submitRequest(EntityEnclosingMethod method, RequestEntity entity) {
827     int statusCode = 0;
828     try {
829       method.setRequestEntity(entity);
830       statusCode = httpClient.executeMethod(method);
831     } catch(HttpException e) {
832       logger.error("Fatal protocol violation: ", e);
833     } catch(IOException e) {
834       logger.error("Fatal transport error: ", e);
835     } catch(Exception e) {
836       logger.error("Unknown exception: ", e);
837     } finally {
838       // Release the connection.
839       method.releaseConnection();
840     }
841     return statusCode;
842   }
843   
844   private StringRequestEntity getXmlEntity(String contents) {
845     if (contents == null) {
846       contents = "";
847     }
848     StringRequestEntity entity = null;
849     final String XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
850     final String XML_CONTENT_TYPE=MediaType.APPLICATION_XML;
851     final String UTF8_CHARSET_NAME = "UTF-8";
852     try {
853       entity =
854         new StringRequestEntity(XML_DECLARATION + contents, XML_CONTENT_TYPE, UTF8_CHARSET_NAME);
855     } catch (UnsupportedEncodingException e) {
856       logger.error("Unsupported character encoding error: ", e);
857     }
858     return entity;
859   }
860
861   private String extractId(ClientResponse<Response> res) {
862     MultivaluedMap mvm = res.getMetadata();
863     String uri = (String) ((ArrayList) mvm.get("Location")).get(0);
864     verbose("extractId:uri=" + uri);
865     String[] segments = uri.split("/");
866     String id = segments[segments.length - 1];
867     verbose("id=" + id);
868     return id;
869   }
870
871   private void verbose(String msg) {
872     if (logger.isDebugEnabled()) {
873       logger.debug(msg);
874     }
875   }
876
877   private void verbose(String msg, Object o, Class clazz) {
878     try{
879       verbose(msg);
880       JAXBContext jc = JAXBContext.newInstance(clazz);
881       Marshaller m = jc.createMarshaller();
882       m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
883           Boolean.TRUE);
884       m.marshal(o, System.out);
885     }catch(Exception e){
886       e.printStackTrace();
887     }
888   }
889
890   private void verboseMap(MultivaluedMap map) {
891     for(Object entry : map.entrySet()){
892       MultivaluedMap.Entry mentry = (MultivaluedMap.Entry) entry;
893       verbose("  name=" + mentry.getKey() + " value=" + mentry.getValue());
894     }
895   }
896
897   private String createIdentifier() {
898     long identifier = System.currentTimeMillis();
899     return Long.toString(identifier);
900   }
901
902   private String createNonExistentIdentifier() {
903     return Long.toString(Long.MAX_VALUE);
904   }
905   
906 }