]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
261a967a01471d0eeba9af0230423ca57f8030dc
[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 package org.collectionspace.services.client.test;
24
25 import java.util.Arrays;
26 import java.util.ArrayList;
27 import java.util.List;
28 import javax.ws.rs.core.MediaType;
29 import javax.ws.rs.core.Response;
30 import org.collectionspace.services.client.CollectionObjectClient;
31 import org.collectionspace.services.client.CollectionSpaceClient;
32 import org.collectionspace.services.collectionobject.BriefDescriptionList;
33 import org.collectionspace.services.collectionobject.CollectionobjectsCommon;
34 import org.collectionspace.services.collectionobject.CollectionobjectsCommonList;
35 import org.collectionspace.services.jaxb.AbstractCommonList;
36 import org.jboss.resteasy.client.ClientResponse;
37 import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput;
38 import org.jboss.resteasy.plugins.providers.multipart.OutputPart;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.testng.Assert;
42 import org.testng.annotations.AfterClass;
43 import org.testng.annotations.BeforeClass;
44 import org.testng.annotations.Test;
45
46 /**
47  * CollectionObjectSearchTest, carries out tests of keyword
48  * search functionality against a deployed and running
49  * CollectionObject Service.
50  *
51  * $LastChangedRevision: 1327 $
52  * $LastChangedDate: 2010-02-12 10:35:11 -0800 (Fri, 12 Feb 2010) $
53  */
54 public class CollectionObjectSearchTest extends BaseServiceTest {
55
56    /** The logger. */
57     private final String CLASS_NAME = CollectionObjectSearchTest.class.getName();
58     private final Logger logger = LoggerFactory.getLogger(CLASS_NAME);
59
60     final static String IDENTIFIER = getSystemTimeIdentifier();
61
62     final static String KEYWORD = "Tsolyani" + IDENTIFIER;
63     final static List<String> TWO_KEYWORDS =
64             Arrays.asList(new String[]{"Cheggarra" + IDENTIFIER, "Ahoggya" + IDENTIFIER});
65     final static List<String> TWO_MORE_KEYWORDS =
66             Arrays.asList(new String[]{"Karihaya" + IDENTIFIER, "Hlikku" + IDENTIFIER});
67     final static String NOISE_WORD = "Mihalli + IDENTIFIER";
68     // Test Unicode UTF-8 term for keyword searching: a random sequence,
69     // unlikely to be encountered in actual collections data, of two USASCII
70     // characters followed by four non-USASCII range Unicode UTF-8 characters:
71     //
72     // Δ : Greek capital letter Delta (U+0394)
73     // Ж : Cyrillic capital letter Zhe with breve (U+04C1)
74     // Ŵ : Latin capital letter W with circumflex (U+0174)
75     // Ω : Greek capital letter Omega (U+03A9)
76
77     final String UTF8_KEYWORD = "to" + '\u0394' + '\u04C1' + '\u0174' +'\u03A9';
78     // Non-existent term unlikely to be encountered in actual collections
79     // data, consisting of two back-to-back sets of the first letters of
80     // each of the words in a short pangram for the English alphabet.
81     final static String NON_EXISTENT_KEYWORD = "jlmbsoqjlmbsoq";
82
83     final static String KEYWORD_SEPARATOR = " ";
84     final long numNoiseWordResources = 10;
85     final double pctNonNoiseWordResources = 0.5;
86
87     /* Use this to keep track of resources to delete */
88     private List<String> allResourceIdsCreated = new ArrayList<String>();
89
90     /* (non-Javadoc)
91      * @see org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent()
92      */
93     @Override
94     protected String getServicePathComponent() {
95         return new CollectionObjectClient().getServicePathComponent();
96     }
97
98     /* (non-Javadoc)
99      * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance()
100      */
101     @Override
102     protected CollectionSpaceClient getClientInstance() {
103         return new CollectionObjectClient();
104     }
105
106     /* (non-Javadoc)
107      * @see org.collectionspace.services.client.test.BaseServiceTest#getAbstractCommonList(org.jboss.resteasy.client.ClientResponse)
108      */
109     @Override
110     protected AbstractCommonList getAbstractCommonList(ClientResponse<AbstractCommonList> response) {
111         return response.getEntity(CollectionobjectsCommonList.class);
112     }
113
114     /**
115      * Creates one or more resources containing a "noise" keyword,
116      * which should NOT be retrieved by keyword searches.
117      *
118      * This also helps ensure that searches will not fail, due
119      * to a database-specific constraint or otherwise, if the
120      * number of records containing a particular keyword represent
121      * too high a proportion of the total number of records.
122      */
123     @BeforeClass(alwaysRun=true)
124     public void setup() {
125         if (logger.isDebugEnabled()) {
126             logger.debug("Creating " + numNoiseWordResources +
127                 " 'noise word' resources ...");
128         }
129         createCollectionObjects(numNoiseWordResources, NOISE_WORD);
130     }
131
132
133     // ---------------------------------------------------------------
134     // Search tests
135     // ---------------------------------------------------------------
136
137     // Success outcomes
138
139     @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class,
140         groups = {"oneKeyword"})
141     public void searchWithOneKeyword(String testName) throws Exception {
142
143         if (logger.isDebugEnabled()) {
144             logger.debug(testBanner(testName, CLASS_NAME));
145         }
146
147         // Create one or more keyword retrievable resources, each containing
148         // a specified keyword.
149         long numKeywordRetrievableResources =
150                 (long) (numNoiseWordResources * pctNonNoiseWordResources);
151         if (logger.isDebugEnabled()) {
152             logger.debug("Creating " + numKeywordRetrievableResources +
153                 " keyword-retrievable resources ...");
154         }
155         createCollectionObjects(numKeywordRetrievableResources, KEYWORD);
156         
157         // Set the expected status code and group of valid status codes
158         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
159
160         // Send the search request and receive a response
161         ClientResponse<CollectionobjectsCommonList> res = doSearch(KEYWORD);
162         int statusCode = res.getStatus();
163
164         // Check the status code of the response: does it match
165         // the expected response(s)?
166         if (logger.isDebugEnabled()) {
167             logger.debug(testName + ": status = " + statusCode);
168         }
169         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
170                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
171         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
172
173         // Verify that the number of resources matched by the search
174         // is identical to the expected result
175         long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
176         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED);
177         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
178     }
179
180     @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class)
181     public void searchWithTwoKeywordsInSameField(String testName) throws Exception {
182
183         if (logger.isDebugEnabled()) {
184             logger.debug(testBanner(testName, CLASS_NAME));
185         }
186
187         // Create one or more keyword retrievable resources, each containing
188         // two specified keywords.
189         long numKeywordRetrievableResources =
190                 (long) (numNoiseWordResources * pctNonNoiseWordResources);
191         if (logger.isDebugEnabled()) {
192             logger.debug("Creating " + numKeywordRetrievableResources +
193                 " keyword-retrievable resources ...");
194         }
195         boolean keywordsInSameField = true;
196         createCollectionObjects(numKeywordRetrievableResources, TWO_KEYWORDS, keywordsInSameField);
197
198         // Set the expected status code and group of valid status codes
199         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
200
201         // Search using both terms
202
203         // Send the search request and receive a response
204         ClientResponse<CollectionobjectsCommonList> res = doSearch(TWO_KEYWORDS);
205         int statusCode = res.getStatus();
206
207         // Check the status code of the response: does it match
208         // the expected response(s)?
209         if (logger.isDebugEnabled()) {
210             logger.debug(testName + ": status = " + statusCode);
211         }
212         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
213                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
214         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
215
216         // Verify that the number of resources matched by the search
217         // is identical to the expected result
218         long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
219         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED);
220         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
221
222         // Search using a single term
223
224         // Send the search request and receive a response
225         res = doSearch(TWO_KEYWORDS.get(0));
226         statusCode = res.getStatus();
227
228         // Check the status code of the response: does it match
229         // the expected response(s)?
230         if (logger.isDebugEnabled()) {
231             logger.debug(testName + ": status = " + statusCode);
232         }
233         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
234                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
235         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
236
237         // Verify that the number of resources matched by the search
238         // is identical to the expected result
239         NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
240         numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED);
241         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
242
243     }
244
245     @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class)
246     public void searchWithTwoKeywordsAcrossTwoFields(String testName) throws Exception {
247
248         if (logger.isDebugEnabled()) {
249             logger.debug(testBanner(testName, CLASS_NAME));
250         }
251
252         // Create one or more keyword retrievable resources, each containing
253         // two specified keywords.
254         long numKeywordRetrievableResources = 5;
255         if (logger.isDebugEnabled()) {
256             logger.debug("Creating " + numKeywordRetrievableResources +
257                 " keyword-retrievable resources ...");
258         }
259         boolean keywordsInSameField = false;
260         createCollectionObjects(numKeywordRetrievableResources, TWO_MORE_KEYWORDS, keywordsInSameField);
261
262         // Set the expected status code and group of valid status codes
263         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
264
265         // Search using both terms
266
267         // Send the search request and receive a response
268         ClientResponse<CollectionobjectsCommonList> res = doSearch(TWO_MORE_KEYWORDS);
269         int statusCode = res.getStatus();
270
271         // Check the status code of the response: does it match
272         // the expected response(s)?
273         if (logger.isDebugEnabled()) {
274             logger.debug(testName + ": status = " + statusCode);
275         }
276         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
277                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
278         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
279
280         // Verify that the number of resources matched by the search
281         // is identical to the expected result
282         long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
283         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED);
284         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
285
286         // Search using a single term
287
288         // Send the search request and receive a response
289         res = doSearch(TWO_MORE_KEYWORDS.get(0));
290         statusCode = res.getStatus();
291
292         // Check the status code of the response: does it match
293         // the expected response(s)?
294         if (logger.isDebugEnabled()) {
295             logger.debug(testName + ": status = " + statusCode);
296         }
297         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
298                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
299         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
300
301         // Verify that the number of resources matched by the search
302         // is identical to the expected result
303         NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
304         numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED);
305         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
306
307     }
308
309 //    @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class)
310 //    public void searchWithOneKeywordInRepeatableScalarField(String testName) throws Exception {
311 //        BriefDescriptionList descriptionList = new BriefDescriptionList();
312 //        List<String> descriptions = descriptionList.getBriefDescription();
313 //        if (TWO_KEYWORDS.size() >= 2) {
314 //            descriptions.add(TWO_KEYWORDS.get(0));
315 //            descriptions.add(TWO_KEYWORDS.get(1));
316 //        }
317 //    }
318
319
320     @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class,
321         groups = {"utf8"})
322     public void searchWithUTF8Keyword(String testName) {
323
324         if (logger.isDebugEnabled()) {
325             logger.debug(testBanner(testName, CLASS_NAME));
326         }
327
328         // Create one or more keyword retrievable resources, each containing
329         // two specified keywords.
330         long numKeywordRetrievableResources = 2;
331         if (logger.isDebugEnabled()) {
332             logger.debug("Creating " + numKeywordRetrievableResources +
333                 " keyword-retrievable resources ...");
334         }
335         createCollectionObjects(numKeywordRetrievableResources, UTF8_KEYWORD);
336
337         // Set the expected status code and group of valid status codes
338         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
339
340         // Send the search request and receive a response
341         ClientResponse<CollectionobjectsCommonList> res = doSearch(UTF8_KEYWORD);
342         int statusCode = res.getStatus();
343
344         // Check the status code of the response: does it match
345         // the expected response(s)?
346         if (logger.isDebugEnabled()) {
347             logger.debug(testName + ": status = " + statusCode);
348         }
349         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
350                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
351         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
352
353         // Verify that the number of resources matched by the search
354         // is identical to the expected result
355         long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
356         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED);
357         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
358     }
359     
360     // Failure outcomes
361
362     // FIXME: Rename to searchWithNonExistentKeyword
363     @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class)
364     public void keywordSearchNonExistentKeyword(String testName) throws Exception {
365
366         if (logger.isDebugEnabled()) {
367             logger.debug(testBanner(testName, CLASS_NAME));
368         }
369
370         // Set the expected status code and group of valid status codes
371         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
372
373         // Send the search request and receive a response
374         ClientResponse<CollectionobjectsCommonList> res = doSearch(NON_EXISTENT_KEYWORD);
375         int statusCode = res.getStatus();
376
377         // Check the status code of the response: does it match
378         // the expected response(s)?
379         if (logger.isDebugEnabled()) {
380             logger.debug(testName + ": status = " + statusCode);
381         }
382         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
383                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
384         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
385
386         // Verify that the number of resources matched by the search
387         // is identical to the expected result
388         long NUM_MATCHES_EXPECTED = 0;
389         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED);
390         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
391
392     }
393
394     // ---------------------------------------------------------------
395     // Cleanup of resources created during testing
396     // ---------------------------------------------------------------
397
398     /**
399      * Deletes all resources created by setup and tests, after all tests have been run.
400      *
401      * This cleanup method will always be run, even if one or more tests fail.
402      * For this reason, it attempts to remove all resources created
403      * at any point during testing, even if some of those resources
404      * may be expected to be deleted by certain tests.
405      */
406     @AfterClass(alwaysRun=true)
407     public void cleanUp() {
408         String noTest = System.getProperty("noTestCleanup");
409         if(Boolean.TRUE.toString().equalsIgnoreCase(noTest)) {
410             if (logger.isDebugEnabled()) {
411                 logger.debug("Skipping Cleanup phase ...");
412             }
413             return;
414         }
415         if (logger.isDebugEnabled()) {
416             logger.debug("Cleaning up temporary resources created for testing ...");
417         }
418         CollectionObjectClient collectionObjectClient = new CollectionObjectClient();
419         for (String resourceId : allResourceIdsCreated) {
420             // Note: Any non-success responses are ignored and not reported.
421             collectionObjectClient.delete(resourceId).releaseConnection();
422         }
423     }
424
425     // ---------------------------------------------------------------
426     // Utility methods used by tests above
427     // ---------------------------------------------------------------
428     
429     private void createCollectionObjects(long numToCreate, String keyword) {
430         List keywords = new ArrayList<String>();
431         keywords.add(keyword);
432         boolean keywordsInSameField = true;
433         createCollectionObjects(numToCreate, keywords, keywordsInSameField);
434     }
435     
436     private void createCollectionObjects(long numToCreate, List<String> keywords,
437             boolean keywordsInSameField) {
438         testSetup(STATUS_CREATED, ServiceRequestType.CREATE);
439         CollectionObjectClient client = new CollectionObjectClient();
440         for (long i = 0; i < numToCreate; i++) {
441             MultipartOutput multipart =
442                     createCollectionObjectInstance(i, keywords, keywordsInSameField);
443             ClientResponse<Response> res = client.create(multipart);
444             try {
445                 int statusCode = res.getStatus();
446                 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
447                 String id = extractId(res);
448                 allResourceIdsCreated.add(id);
449                 if (logger.isDebugEnabled()) {
450                     logger.debug("Created new resource [" + i + "] with ID " + id);
451                 }
452             } finally {
453                 res.releaseConnection();
454             }
455         }
456     }
457     
458     private MultipartOutput createCollectionObjectInstance(long i, List<String> keywords,
459             boolean keywordsInSameField) {
460         CollectionobjectsCommon collectionObject = new CollectionobjectsCommon();
461         collectionObject.setObjectNumber(createIdentifier());
462         if (keywordsInSameField) {
463             collectionObject.setDistinguishingFeatures(listToString(keywords, KEYWORD_SEPARATOR));
464         } else {
465             if (keywords.size() == 1) {
466                 collectionObject.setDistinguishingFeatures(keywords.get(0));
467             } else if (keywords.size() == 2) {
468                 collectionObject.setDistinguishingFeatures(keywords.get(0));
469                 collectionObject.setPhysicalDescription(keywords.get(1));
470             } else {
471                 Assert.fail("List of keywords must have exactly one or two members.");
472             }
473         }
474         MultipartOutput multipart = new MultipartOutput();
475         OutputPart commonPart = multipart.addPart(collectionObject,
476                 MediaType.APPLICATION_XML_TYPE);
477         commonPart.getHeaders().add("label", new CollectionObjectClient().getCommonPartName());
478         return multipart;
479     }
480
481     private static String listToString(List<String> list, String separator) {
482         StringBuffer sb = new StringBuffer();
483         if (list.size() > 0) {
484             sb.append(list.get(0));
485             for (int i=1; i < list.size(); i++) {
486                 sb.append(separator);
487                 sb.append(list.get(i));
488             }
489         }
490         return sb.toString();
491     }
492
493     private ClientResponse<CollectionobjectsCommonList> doSearch(List<String> keywords) {
494         String searchParamValue = listToString(keywords, KEYWORD_SEPARATOR);
495         return doSearch(searchParamValue);
496     }
497
498     private ClientResponse<CollectionobjectsCommonList> doSearch(String keyword) {
499        String searchParamValue = keyword;
500         if (logger.isDebugEnabled()) {
501             logger.debug("Searching on keyword(s): " + searchParamValue + " ...");
502         }
503         CollectionObjectClient client = new CollectionObjectClient();
504         ClientResponse<CollectionobjectsCommonList> res =
505             client.keywordSearch(searchParamValue);
506         return res;
507     }
508
509     private long getNumMatched(ClientResponse<CollectionobjectsCommonList> res,
510             long numExpectedMatches) {
511         CollectionobjectsCommonList list = (CollectionobjectsCommonList)
512             res.getEntity(CollectionobjectsCommonList.class);
513         long numMatched = list.getTotalItems();
514
515         if (logger.isDebugEnabled()) {
516             logger.debug("Keyword search matched " + numMatched +
517                 " resources, expected to match " + numExpectedMatches);
518         }
519
520         // Optionally output additional data about list members for debugging.
521         boolean iterateThroughList = false;
522         if (iterateThroughList && logger.isDebugEnabled()) {
523             itemizeListItems(list);
524         }
525         return numMatched;
526     }
527
528     private void itemizeListItems(CollectionobjectsCommonList list) {
529         List<CollectionobjectsCommonList.CollectionObjectListItem> items =
530             list.getCollectionObjectListItem();
531         int i = 0;
532         for (CollectionobjectsCommonList.CollectionObjectListItem item : items) {
533             logger.debug("list-item[" + i + "] title="
534                     + item.getTitle());
535             logger.debug("list-item[" + i + "] URI="
536                     + item.getUri());
537             i++;
538         }
539     }
540
541     public static String getSystemTimeIdentifier() {
542       return Long.toString(System.currentTimeMillis());
543     }
544
545 }