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
15 * https://source.collectionspace.org/collection-space/LICENSE.txt
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.
23 package org.collectionspace.services.client.test;
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;
47 * CollectionObjectSearchTest, carries out tests of keyword
48 * search functionality against a deployed and running
49 * CollectionObject Service.
51 * $LastChangedRevision: 1327 $
52 * $LastChangedDate: 2010-02-12 10:35:11 -0800 (Fri, 12 Feb 2010) $
54 public class CollectionObjectSearchTest extends BaseServiceTest {
57 private final String CLASS_NAME = CollectionObjectSearchTest.class.getName();
58 private final Logger logger = LoggerFactory.getLogger(CLASS_NAME);
60 final static String IDENTIFIER = getSystemTimeIdentifier();
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:
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)
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";
83 final static String KEYWORD_SEPARATOR = " ";
84 final long numNoiseWordResources = 10;
85 final double pctNonNoiseWordResources = 0.5;
87 /* Use this to keep track of resources to delete */
88 private List<String> allResourceIdsCreated = new ArrayList<String>();
91 * @see org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent()
94 protected String getServicePathComponent() {
95 return new CollectionObjectClient().getServicePathComponent();
99 * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance()
102 protected CollectionSpaceClient getClientInstance() {
103 return new CollectionObjectClient();
107 * @see org.collectionspace.services.client.test.BaseServiceTest#getAbstractCommonList(org.jboss.resteasy.client.ClientResponse)
110 protected AbstractCommonList getAbstractCommonList(ClientResponse<AbstractCommonList> response) {
111 return response.getEntity(CollectionobjectsCommonList.class);
115 * Creates one or more resources containing a "noise" keyword,
116 * which should NOT be retrieved by keyword searches.
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.
123 @BeforeClass(alwaysRun=true)
124 public void setup() {
125 if (logger.isDebugEnabled()) {
126 logger.debug("Creating " + numNoiseWordResources +
127 " 'noise word' resources ...");
129 createCollectionObjects(numNoiseWordResources, NOISE_WORD);
133 // ---------------------------------------------------------------
135 // ---------------------------------------------------------------
139 @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class,
140 groups = {"oneKeyword"})
141 public void searchWithOneKeyword(String testName) throws Exception {
143 if (logger.isDebugEnabled()) {
144 logger.debug(testBanner(testName, CLASS_NAME));
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 ...");
155 createCollectionObjects(numKeywordRetrievableResources, KEYWORD);
157 // Set the expected status code and group of valid status codes
158 testSetup(STATUS_OK, ServiceRequestType.SEARCH);
160 // Send the search request and receive a response
161 ClientResponse<CollectionobjectsCommonList> res = doSearch(KEYWORD);
162 int statusCode = res.getStatus();
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);
169 Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
170 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
171 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
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);
180 @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class)
181 public void searchWithTwoKeywordsInSameField(String testName) throws Exception {
183 if (logger.isDebugEnabled()) {
184 logger.debug(testBanner(testName, CLASS_NAME));
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 ...");
195 boolean keywordsInSameField = true;
196 createCollectionObjects(numKeywordRetrievableResources, TWO_KEYWORDS, keywordsInSameField);
198 // Set the expected status code and group of valid status codes
199 testSetup(STATUS_OK, ServiceRequestType.SEARCH);
201 // Search using both terms
203 // Send the search request and receive a response
204 ClientResponse<CollectionobjectsCommonList> res = doSearch(TWO_KEYWORDS);
205 int statusCode = res.getStatus();
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);
212 Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
213 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
214 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
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);
222 // Search using a single term
224 // Send the search request and receive a response
225 res = doSearch(TWO_KEYWORDS.get(0));
226 statusCode = res.getStatus();
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);
233 Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
234 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
235 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
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);
245 @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class)
246 public void searchWithTwoKeywordsAcrossTwoFields(String testName) throws Exception {
248 if (logger.isDebugEnabled()) {
249 logger.debug(testBanner(testName, CLASS_NAME));
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 ...");
259 boolean keywordsInSameField = false;
260 createCollectionObjects(numKeywordRetrievableResources, TWO_MORE_KEYWORDS, keywordsInSameField);
262 // Set the expected status code and group of valid status codes
263 testSetup(STATUS_OK, ServiceRequestType.SEARCH);
265 // Search using both terms
267 // Send the search request and receive a response
268 ClientResponse<CollectionobjectsCommonList> res = doSearch(TWO_MORE_KEYWORDS);
269 int statusCode = res.getStatus();
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);
276 Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
277 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
278 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
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);
286 // Search using a single term
288 // Send the search request and receive a response
289 res = doSearch(TWO_MORE_KEYWORDS.get(0));
290 statusCode = res.getStatus();
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);
297 Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
298 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
299 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
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);
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));
320 @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class,
322 public void searchWithUTF8Keyword(String testName) {
324 if (logger.isDebugEnabled()) {
325 logger.debug(testBanner(testName, CLASS_NAME));
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 ...");
335 createCollectionObjects(numKeywordRetrievableResources, UTF8_KEYWORD);
337 // Set the expected status code and group of valid status codes
338 testSetup(STATUS_OK, ServiceRequestType.SEARCH);
340 // Send the search request and receive a response
341 ClientResponse<CollectionobjectsCommonList> res = doSearch(UTF8_KEYWORD);
342 int statusCode = res.getStatus();
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);
349 Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
350 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
351 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
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);
362 // FIXME: Rename to searchWithNonExistentKeyword
363 @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class)
364 public void keywordSearchNonExistentKeyword(String testName) throws Exception {
366 if (logger.isDebugEnabled()) {
367 logger.debug(testBanner(testName, CLASS_NAME));
370 // Set the expected status code and group of valid status codes
371 testSetup(STATUS_OK, ServiceRequestType.SEARCH);
373 // Send the search request and receive a response
374 ClientResponse<CollectionobjectsCommonList> res = doSearch(NON_EXISTENT_KEYWORD);
375 int statusCode = res.getStatus();
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);
382 Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
383 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
384 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
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);
394 // ---------------------------------------------------------------
395 // Cleanup of resources created during testing
396 // ---------------------------------------------------------------
399 * Deletes all resources created by setup and tests, after all tests have been run.
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.
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 ...");
415 if (logger.isDebugEnabled()) {
416 logger.debug("Cleaning up temporary resources created for testing ...");
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();
425 // ---------------------------------------------------------------
426 // Utility methods used by tests above
427 // ---------------------------------------------------------------
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);
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);
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);
453 res.releaseConnection();
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));
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));
471 Assert.fail("List of keywords must have exactly one or two members.");
474 MultipartOutput multipart = new MultipartOutput();
475 OutputPart commonPart = multipart.addPart(collectionObject,
476 MediaType.APPLICATION_XML_TYPE);
477 commonPart.getHeaders().add("label", new CollectionObjectClient().getCommonPartName());
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));
490 return sb.toString();
493 private ClientResponse<CollectionobjectsCommonList> doSearch(List<String> keywords) {
494 String searchParamValue = listToString(keywords, KEYWORD_SEPARATOR);
495 return doSearch(searchParamValue);
498 private ClientResponse<CollectionobjectsCommonList> doSearch(String keyword) {
499 String searchParamValue = keyword;
500 if (logger.isDebugEnabled()) {
501 logger.debug("Searching on keyword(s): " + searchParamValue + " ...");
503 CollectionObjectClient client = new CollectionObjectClient();
504 ClientResponse<CollectionobjectsCommonList> res =
505 client.keywordSearch(searchParamValue);
509 private long getNumMatched(ClientResponse<CollectionobjectsCommonList> res,
510 long numExpectedMatches) {
511 CollectionobjectsCommonList list = (CollectionobjectsCommonList)
512 res.getEntity(CollectionobjectsCommonList.class);
513 long numMatched = list.getTotalItems();
515 if (logger.isDebugEnabled()) {
516 logger.debug("Keyword search matched " + numMatched +
517 " resources, expected to match " + numExpectedMatches);
520 // Optionally output additional data about list members for debugging.
521 boolean iterateThroughList = false;
522 if (iterateThroughList && logger.isDebugEnabled()) {
523 itemizeListItems(list);
528 private void itemizeListItems(CollectionobjectsCommonList list) {
529 List<CollectionobjectsCommonList.CollectionObjectListItem> items =
530 list.getCollectionObjectListItem();
532 for (CollectionobjectsCommonList.CollectionObjectListItem item : items) {
533 logger.debug("list-item[" + i + "] title="
535 logger.debug("list-item[" + i + "] URI="
541 public static String getSystemTimeIdentifier() {
542 return Long.toString(System.currentTimeMillis());