001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.integration.http.api; 019 020import com.fasterxml.jackson.databind.ObjectMapper; 021import org.apache.http.client.methods.CloseableHttpResponse; 022import org.apache.http.client.methods.HttpDelete; 023import org.apache.http.client.methods.HttpGet; 024import org.apache.http.client.methods.HttpPatch; 025import org.apache.http.entity.StringEntity; 026import org.fcrepo.search.api.Condition; 027import org.fcrepo.search.api.SearchResult; 028import org.junit.Test; 029import org.springframework.test.context.TestExecutionListeners; 030 031import java.io.IOException; 032import java.net.URLEncoder; 033import java.nio.charset.StandardCharsets; 034import java.time.Duration; 035import java.time.Instant; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collections; 039import java.util.List; 040import java.util.Map; 041import java.util.concurrent.TimeUnit; 042import java.util.stream.Collectors; 043import java.util.stream.Stream; 044 045import static javax.ws.rs.core.Response.Status.BAD_REQUEST; 046import static javax.ws.rs.core.Response.Status.NO_CONTENT; 047import static javax.ws.rs.core.Response.Status.OK; 048import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_ID_PREFIX; 049import static org.fcrepo.search.api.Condition.Field.CONTENT_SIZE; 050import static org.fcrepo.search.api.Condition.Field.CREATED; 051import static org.fcrepo.search.api.Condition.Field.FEDORA_ID; 052import static org.fcrepo.search.api.Condition.Field.MIME_TYPE; 053import static org.fcrepo.search.api.Condition.Field.MODIFIED; 054import static org.fcrepo.search.api.Condition.Field.RDF_TYPE; 055import static org.junit.Assert.assertEquals; 056import static org.junit.Assert.assertNotEquals; 057import static org.junit.Assert.assertNotNull; 058import static org.junit.Assert.assertTrue; 059 060/** 061 * @author dbernstein 062 * @since 05/06/20 063 */ 064@TestExecutionListeners( 065 listeners = { TestIsolationExecutionListener.class }, 066 mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 067public class FedoraSearchIT extends AbstractResourceIT { 068 069 private static final ObjectMapper objectMapper = new ObjectMapper(); 070 071 private String getSearchEndpoint() { 072 return serverAddress + "fcr:search?"; 073 } 074 075 private List<String> createResources(final int count) throws IOException { 076 return createResources(getRandomUniqueId(), count); 077 } 078 079 private List<String> createResources(final String prefix, final int count) throws IOException { 080 final var resources = new ArrayList<String>(); 081 for (int i = 0; i < count; i++) { 082 try (final CloseableHttpResponse response = createObject(prefix + "-" + String.format("%05d", i))) { 083 resources.add(getLocation(response)); 084 } 085 } 086 return resources; 087 } 088 089 @Test 090 public void testSearchAllResources() throws Exception { 091 final var resources = createResources(3); 092 final var condition = FEDORA_ID + "=*"; 093 List<String> notFound = resources; 094 095 final int maxResults = 10; 096 int offset = 0; 097 098 // Loop until all resources are found 099 // - Failure case: loop dies with assertion for non-zero results in 'doSearchAllResources()' 100 while (!notFound.isEmpty()) { 101 notFound = doSearchAllResources(notFound, condition, maxResults, offset); 102 offset += maxResults; 103 } 104 } 105 106 private List<String> doSearchAllResources(final List<String> resources, 107 final String condition, 108 final int maxResults, 109 final int offset) throws IOException { 110 final String searchUrl = getSearchEndpoint() 111 + "condition=" + encode(condition) + "&max_results=" + maxResults + "&offset=" + offset; 112 113 final List<String> notFound = new ArrayList<>(); 114 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 115 assertEquals(OK.getStatusCode(), getStatus(response)); 116 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 117 assertNotNull(result); 118 assertNotNull(result.getPagination()); 119 assertTrue("Items not found! " + resources, result.getItems().size() > 0); 120 final var resultingFedoraIds = 121 result.getItems().stream().map(x -> x.get("fedora_id")).collect(Collectors.toList()); 122 123 resources.forEach(r -> { 124 if (!resultingFedoraIds.contains(r)) { 125 notFound.add(r); 126 } 127 }); 128 } 129 return notFound; 130 } 131 132 @Test 133 public void testWildcardMatchingOnPartialFedoraId() throws Exception { 134 final var id = getRandomUniqueId(); 135 final int count = 3; 136 createResources(id, count); 137 final var urlPrefix = serverAddress + id; 138 // try all valid prefix formulations 139 final var prefixes = new String[]{id, "/" + id, urlPrefix, FEDORA_ID_PREFIX + "/" + id}; 140 for (final String prefix : prefixes) { 141 final var condition = FEDORA_ID + "=" + prefix + "*"; 142 final String searchUrl = getSearchEndpoint() + "condition=" + encode(condition); 143 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 144 assertEquals(OK.getStatusCode(), getStatus(response)); 145 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 146 SearchResult.class); 147 assertEquals("expected " + count + " items where condition = " + condition, count, 148 result.getItems().size()); 149 150 assertTrue(result.getItems().stream().map(x -> x.get(FEDORA_ID.toString())) 151 .allMatch(x -> x.toString().startsWith(urlPrefix))); 152 } 153 } 154 } 155 156 @Test 157 public void testUpdateSearchIndexOnResourceUpdate() throws Exception { 158 final var resourceId = createResources(1).get(0); 159 final var condition = FEDORA_ID + "=" + resourceId; 160 final String searchUrl = getSearchEndpoint() + "condition=" + encode(condition); 161 String modified = null; 162 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 163 assertEquals(OK.getStatusCode(), getStatus(response)); 164 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 165 SearchResult.class); 166 assertEquals("expected 1 item where condition = " + condition, 1, 167 result.getItems().size()); 168 modified = result.getItems().get(0).get(MODIFIED.toString()).toString(); 169 } 170 171 TimeUnit.SECONDS.sleep(1); 172 173 final var patch = new HttpPatch(resourceId); 174 patch.setHeader("Content-Type", "application/sparql-update"); 175 patch.setEntity(new StringEntity("insert data { <> <http://example/blah> \"Blah\". }")); 176 try (final CloseableHttpResponse response = execute(patch)) { 177 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 178 } 179 180 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 181 assertEquals(OK.getStatusCode(), getStatus(response)); 182 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 183 SearchResult.class); 184 assertEquals("expected 1 item where condition = " + condition, 1, 185 result.getItems().size()); 186 final var newModified = result.getItems().get(0).get(MODIFIED.toString()).toString(); 187 assertNotEquals("Modified date should have changed but it did not.", modified, newModified); 188 } 189 } 190 191 @Test 192 public void testUpdateSearchIndexOnResourceDelete() throws Exception { 193 final var resourceId = createResources(1).get(0); 194 final var condition = FEDORA_ID + "=" + resourceId; 195 final String searchUrl = getSearchEndpoint() + "condition=" + encode(condition); 196 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 197 assertEquals(OK.getStatusCode(), getStatus(response)); 198 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 199 SearchResult.class); 200 assertEquals("expected 1 item where condition = " + condition, 1, 201 result.getItems().size()); 202 } 203 204 final var httpDelete = new HttpDelete(resourceId); 205 try (final CloseableHttpResponse response = execute(httpDelete)) { 206 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 207 } 208 209 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 210 assertEquals(OK.getStatusCode(), getStatus(response)); 211 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 212 SearchResult.class); 213 assertEquals("expected 0 items where condition = " + condition, 0, 214 result.getItems().size()); 215 } 216 } 217 218 public void testModifiedGreaterAndLessThan() throws Exception { 219 final var id = getRandomUniqueId(); 220 final int count = 1; 221 final var instant = Instant.now(); 222 final var now = instant.toString(); 223 final var tomorrow = instant.plus(Duration.ofDays(1)).toString(); 224 createResources(id, count); 225 final var fedoraId = FEDORA_ID_PREFIX + "/" + id; 226 final var fedoraIdCondition = FEDORA_ID + "=" + fedoraId; 227 final var lessThanNow = MODIFIED + "<" + now; 228 final var greaterThanNow = MODIFIED + ">" + now; 229 final var lessThanTomorrow = MODIFIED + "<" + tomorrow; 230 final var greaterThanTomorrow = MODIFIED + ">" + tomorrow; 231 232 //no results for resources modified before now 233 String searchUrl = 234 getSearchEndpoint() + "condition=" + encode(fedoraIdCondition) + "&" + lessThanNow; 235 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 236 assertEquals(OK.getStatusCode(), getStatus(response)); 237 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 238 SearchResult.class); 239 assertEquals("expected no results", 0, 240 result.getItems().size()); 241 } 242 //no results for resources modified after tomorrow 243 searchUrl = 244 getSearchEndpoint() + "condition=" + encode(fedoraIdCondition) + "&" + greaterThanTomorrow; 245 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 246 assertEquals(OK.getStatusCode(), getStatus(response)); 247 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 248 SearchResult.class); 249 assertEquals("expected no results", 0, 250 result.getItems().size()); 251 } 252 253 // ensure that greater than now returns a result 254 searchUrl = getSearchEndpoint() + "condition=" + encode(fedoraIdCondition) + "&" + greaterThanNow; 255 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 256 assertEquals(OK.getStatusCode(), getStatus(response)); 257 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 258 SearchResult.class); 259 assertEquals("expected " + count + " results", count, 260 result.getItems().size()); 261 262 assertTrue(result.getItems().stream().map(x -> x.get("fedora_id")) 263 .allMatch(x -> x.toString().equals(fedoraId))); 264 } 265 266 // ensure that less than tomorrow returns a result 267 searchUrl = getSearchEndpoint() + "condition=" + encode(fedoraIdCondition) + "&" + lessThanTomorrow; 268 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 269 assertEquals(OK.getStatusCode(), getStatus(response)); 270 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 271 SearchResult.class); 272 assertEquals("expected " + count + " results", count, 273 result.getItems().size()); 274 275 assertTrue(result.getItems().stream().map(x -> x.get("fedora_id")) 276 .allMatch(x -> x.toString().equals(fedoraId))); 277 } 278 279 // ensure that between now and tomorrow returns a result. 280 searchUrl = getSearchEndpoint() + "condition=" + encode(fedoraIdCondition) + "&" + greaterThanNow + 281 "&" + lessThanTomorrow; 282 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 283 assertEquals(OK.getStatusCode(), getStatus(response)); 284 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), 285 SearchResult.class); 286 assertEquals("expected " + count + " results ", count, 287 result.getItems().size()); 288 assertTrue(result.getItems().stream().map(x -> x.get("fedora_id")) 289 .allMatch(x -> x.toString().equals(fedoraId))); 290 } 291 } 292 293 @Test 294 public void testFieldsReturnsSpecifiedFields() throws Exception { 295 testFieldsReturnsOnlySpecifiedFields(Arrays.asList(FEDORA_ID, CREATED)); 296 } 297 298 @Test 299 public void testFieldsReturnsMimeType() throws Exception { 300 testFieldsReturnsOnlySpecifiedFields(Arrays.asList(MIME_TYPE)); 301 } 302 303 @Test 304 public void testFieldsReturnsContentSize() throws Exception { 305 testFieldsReturnsOnlySpecifiedFields(Arrays.asList(CONTENT_SIZE)); 306 } 307 308 @Test 309 public void testFieldsReturnsRDFType() throws Exception { 310 testFieldsReturnsOnlySpecifiedFields(Arrays.asList(RDF_TYPE)); 311 } 312 313 314 private void testFieldsReturnsOnlySpecifiedFields(final List<Condition.Field> fieldList) throws Exception { 315 final var id = getRandomUniqueId(); 316 final var count = 1; 317 createResources(id, count); 318 final var fields = fieldList.stream() 319 .map(Condition.Field::toString).collect(Collectors.toList()); 320 final String searchUrl = 321 getSearchEndpoint() + "condition=" + encode("fedora_id=" + id + "*") + "&fields=" + String.join(",", 322 fields); 323 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 324 assertEquals(OK.getStatusCode(), getStatus(response)); 325 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 326 final var items = result.getItems(); 327 assertEquals("expected " + count + " items", count, items.size()); 328 for (final Map<String, Object> item : items) { 329 assertEquals("expected " + fields.size() + " fields returned", fields.size(), item.size()); 330 for (final String field : fields) { 331 assertTrue("Result does not contain expected field: " + field, item.containsKey(field)); 332 } 333 } 334 } 335 } 336 337 @Test 338 public void testMaxResultsAndOffset() throws Exception { 339 final var prefix = getRandomUniqueId(); 340 final int count = 3; 341 final var resources = createResources(prefix, count); 342 final var condition = FEDORA_ID + "=" + prefix + "*"; 343 final var maxResults = 1; 344 final String searchUrl = 345 getSearchEndpoint() + "condition=" + encode(condition) + "&max_results=" + maxResults + "&offset=2" + 346 "&order_by=fedora_id"; 347 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 348 assertEquals(OK.getStatusCode(), getStatus(response)); 349 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 350 assertEquals(maxResults, result.getItems().size()); 351 final var returnedIds = 352 result.getItems().stream().map(x -> x.get("fedora_id")).collect(Collectors.toList()); 353 assertEquals(resources.get(2), returnedIds.get(0)); 354 } 355 } 356 357 @Test 358 public void testDoNotIncludeTotalCount() throws Exception { 359 final var prefix = getRandomUniqueId(); 360 final int count = 3; 361 createResources(prefix, count); 362 final var condition = FEDORA_ID + "=" + prefix + "*"; 363 final var maxResults = 1; 364 final String searchUrl = 365 getSearchEndpoint() + "condition=" + encode(condition) + "&max_results=" + maxResults + 366 "&include_total_result_count=false"; 367 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 368 assertEquals(OK.getStatusCode(), getStatus(response)); 369 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 370 assertEquals(maxResults, result.getItems().size()); 371 assertEquals(-1, result.getPagination().getTotalResults()); 372 } 373 } 374 375 @Test 376 public void testIncludeTotalCount() throws Exception { 377 final var prefix = getRandomUniqueId(); 378 final int count = 3; 379 createResources(prefix, count); 380 final var condition = FEDORA_ID + "=" + prefix + "*"; 381 final var maxResults = 1; 382 final String searchUrl = 383 getSearchEndpoint() + "condition=" + encode(condition) + "&max_results=" + maxResults + 384 "&include_total_result_count=true"; 385 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 386 assertEquals(OK.getStatusCode(), getStatus(response)); 387 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 388 assertEquals(maxResults, result.getItems().size()); 389 assertEquals(count, result.getPagination().getTotalResults()); 390 } 391 } 392 393 @Test 394 public void testSearchByContentSize() throws Exception { 395 final var resourceId = getRandomUniqueId(); 396 createObjectAndClose(resourceId); 397 final var ds1 = "test"; 398 createDatastream(resourceId, ds1, ds1); 399 final var ds2 = "test-longer-length"; 400 createDatastream(resourceId, ds2, ds2); 401 final var condition = FEDORA_ID + "=" + resourceId + "*"; 402 final var contentSizeCondition = CONTENT_SIZE + ">=" + ds1.getBytes().length; 403 final var contentSizeConditionLongerLength = CONTENT_SIZE + ">=" + ds2.getBytes().length; 404 final String searchUrl = 405 getSearchEndpoint() + "condition=" + encode(condition) + "&condition=" + encode(contentSizeCondition); 406 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 407 assertEquals(OK.getStatusCode(), getStatus(response)); 408 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 409 assertEquals(2, result.getItems().size()); 410 } 411 412 final String searchUrl2 = 413 getSearchEndpoint() + "condition=" + encode(condition) + 414 "&condition=" + encode(contentSizeConditionLongerLength); 415 416 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl2))) { 417 assertEquals(OK.getStatusCode(), getStatus(response)); 418 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 419 assertEquals(1, result.getItems().size()); 420 } 421 } 422 423 @Test 424 public void testSearchByMimeType() throws Exception { 425 final var resourceId = getRandomUniqueId(); 426 createObjectAndClose(resourceId); 427 final var ds1 = "test"; 428 createDatastream(resourceId, ds1, ds1); 429 final var condition = FEDORA_ID + "=" + resourceId + "*"; 430 final var matchingMimetypeCondition = MIME_TYPE + "=text/plain"; 431 final var nonMatchingMimetypeCondition = MIME_TYPE + "=image/jpg"; 432 final String searchUrl = 433 getSearchEndpoint() + "condition=" + encode(condition) + 434 "&condition=" + encode(matchingMimetypeCondition); 435 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 436 assertEquals(OK.getStatusCode(), getStatus(response)); 437 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 438 439 assertEquals(1, result.getItems().size()); 440 } 441 442 final String searchUrl2 = 443 getSearchEndpoint() + "condition=" + encode(condition) + 444 "&condition=" + encode(nonMatchingMimetypeCondition); 445 446 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl2))) { 447 assertEquals(OK.getStatusCode(), getStatus(response)); 448 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 449 assertEquals(0, result.getItems().size()); 450 } 451 } 452 453 @Test 454 public void testSearchNoMatchingFedoraIds() throws Exception { 455 final var condition = FEDORA_ID + "=" + serverAddress + "this-should-not-match-any-fedora-id"; 456 final String searchUrl = getSearchEndpoint() + "condition=" + encode(condition); 457 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 458 assertEquals(OK.getStatusCode(), getStatus(response)); 459 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 460 assertNotNull(result); 461 assertNotNull(result.getPagination()); 462 assertEquals("No results expected.", 0, result.getItems().size()); 463 } 464 } 465 466 @Test 467 public void testMalformedCondition() throws Exception { 468 final var condition = "this_is_not_a_valid_condition"; 469 final String searchUrl = getSearchEndpoint() + "condition=" + encode(condition); 470 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 471 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(response)); 472 } 473 } 474 475 @Test 476 public void testOrderingISAscendingByDefaultIfOrderByIsDefined() throws Exception { 477 final var prefix = getRandomUniqueId(); 478 final var resources = createResources(prefix, 3); 479 final var condition = FEDORA_ID + "=" + prefix + "*"; 480 final String searchUrl = getSearchEndpoint() + "condition=" + encode(condition) + "&order_by=fedora_id"; 481 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 482 assertEquals(OK.getStatusCode(), getStatus(response)); 483 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 484 assertEquals(resources, result.getItems().stream() 485 .map(x -> x.get("fedora_id")).collect(Collectors.toList())); 486 } 487 } 488 489 @Test 490 public void testDescOrdering() throws Exception { 491 final var prefix = getRandomUniqueId(); 492 final var resources = createResources(prefix, 3); 493 Collections.reverse(resources); 494 final var condition = FEDORA_ID + "=" + prefix + "*"; 495 final String searchUrl = getSearchEndpoint() + "condition=" + encode(condition) + 496 "&order_by=fedora_id&order=desc"; 497 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 498 assertEquals(OK.getStatusCode(), getStatus(response)); 499 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 500 assertEquals(resources, result.getItems().stream() 501 .map(x -> x.get("fedora_id")).collect(Collectors.toList())); 502 } 503 } 504 505 @Test 506 public void testOrderByMimetypeDesc() throws Exception { 507 final var resourceId = getRandomUniqueId(); 508 createObjectAndClose(resourceId); 509 final var resourceA = resourceId + "/a-video"; 510 final var resourceB = resourceId + "/b-image"; 511 final var resourceC = resourceId + "/c-text"; 512 513 assertEquals(201, getStatus(putObjMethod(resourceA, "video/mp4", 514 "video"))); 515 assertEquals(201, getStatus(putObjMethod(resourceB, "image/jpg", 516 "image"))); 517 assertEquals(201, getStatus(putObjMethod(resourceC, "text/plain", 518 "text"))); 519 520 final var resources = 521 Stream.of(resourceA, resourceC, resourceB).map(x -> serverAddress + x) 522 .collect(Collectors.toList()); 523 final var condition = FEDORA_ID + "=" + resourceId + "/*"; 524 final String searchUrl = getSearchEndpoint() + "condition=" + encode(condition) + 525 "&order_by=mime_type&order=desc"; 526 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 527 assertEquals(OK.getStatusCode(), getStatus(response)); 528 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 529 assertEquals(resources, result.getItems().stream() 530 .map(x -> x.get("fedora_id")).collect(Collectors.toList())); 531 } 532 } 533 534 535 @Test 536 public void testSearchByRdfSourceWildCards() throws Exception { 537 testSearchByRdfTypeParam("*Basic*"); 538 } 539 540 @Test 541 public void testSearchByRdfSourceWildcard() throws Exception { 542 testSearchByRdfTypeParam("*Container"); 543 } 544 545 @Test 546 public void testSearchByRdfSourceExactMatch() throws Exception { 547 testSearchByRdfTypeParam("http://www.w3.org/ns/ldp#RDFSource"); 548 } 549 550 private void testSearchByRdfTypeParam(final String rdfTypeString) throws Exception { 551 final var resourceId = getRandomUniqueId(); 552 createObjectAndClose(resourceId); 553 final var condition = FEDORA_ID + "=" + resourceId; 554 final var rdfTypeCondition = RDF_TYPE + "=" + rdfTypeString; 555 final String searchUrl = 556 getSearchEndpoint() + "condition=" + encode(condition) + "&condition=" + encode(rdfTypeCondition); 557 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl))) { 558 assertEquals(OK.getStatusCode(), getStatus(response)); 559 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 560 assertEquals(1, result.getItems().size()); 561 } 562 563 final String searchUrl2 = 564 getSearchEndpoint() + "condition=" + encode(condition) + 565 "&condition=" + encode(rdfTypeCondition + "with-non-matching-suffix"); 566 567 try (final CloseableHttpResponse response = execute(new HttpGet(searchUrl2))) { 568 assertEquals(OK.getStatusCode(), getStatus(response)); 569 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 570 assertEquals(0, result.getItems().size()); 571 } 572 } 573 574 private String encode(final String value) { 575 return URLEncoder.encode(value, StandardCharsets.UTF_8); 576 } 577 578 @Test 579 public void testTotalResults() throws Exception { 580 final String prefix = "test_total_"; 581 final var resources = createResources(prefix, 6); 582 final var condition = FEDORA_ID + "=" + prefix + "*"; 583 final var maxResults = 2; 584 final String searchUrl = 585 getSearchEndpoint() + "include_total_result_count=true&condition=" + encode(condition) + 586 "&max_results=" + maxResults; 587 try (final var response = execute(new HttpGet(searchUrl))) { 588 assertEquals(OK.getStatusCode(), getStatus(response)); 589 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 590 assertEquals(2, result.getItems().size()); 591 assertEquals(6, result.getPagination().getTotalResults()); 592 } 593 // Try the next offset and see the total is the same 594 final String searchUrl2 = searchUrl + "&offset=2"; 595 try (final var response = execute(new HttpGet(searchUrl2))) { 596 assertEquals(OK.getStatusCode(), getStatus(response)); 597 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 598 assertEquals(2, result.getItems().size()); 599 assertEquals(6, result.getPagination().getTotalResults()); 600 } 601 } 602 603 @Test 604 public void testTotalResultsMore() throws Exception { 605 final String prefix = "test_total1_"; 606 final String prefix2 = "test_total2_"; 607 final var resources = createResources(prefix, 6); 608 final var resources2 = createResources(prefix2, 6); 609 final var condition = FEDORA_ID + "=" + prefix + "*"; 610 final var maxResults = 5; 611 final String searchUrl = 612 getSearchEndpoint() + "include_total_result_count=true&condition=" + encode(condition) + 613 "&max_results=" + maxResults; 614 try (final var response = execute(new HttpGet(searchUrl))) { 615 assertEquals(OK.getStatusCode(), getStatus(response)); 616 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 617 assertEquals(5, result.getItems().size()); 618 assertEquals(6, result.getPagination().getTotalResults()); 619 assertTrue(result.getItems().stream().allMatch(r -> resources.contains(r.get("fedora_id").toString()) && 620 !resources2.contains(r.get("fedora_id").toString()))); 621 } 622 // Try the next offset and see the total is the same 623 final String searchUrl2 = searchUrl + "&offset=5"; 624 try (final var response = execute(new HttpGet(searchUrl2))) { 625 assertEquals(OK.getStatusCode(), getStatus(response)); 626 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 627 assertEquals(1, result.getItems().size()); 628 assertEquals(6, result.getPagination().getTotalResults()); 629 assertTrue(result.getItems().stream().allMatch(r -> resources.contains(r.get("fedora_id").toString()) && 630 !resources2.contains(r.get("fedora_id").toString()))); 631 } 632 } 633 634 @Test 635 public void testUnderScoreEscaping() throws Exception { 636 final String prefix = "test_total_"; 637 final String prefix2 = "test_total2_"; 638 final String resource1; 639 final String resource2; 640 try (final var response = createObject(prefix + "-1")) { 641 resource1 = getLocation(response); 642 } 643 try (final var response = createObject(prefix2 + "-1")) { 644 resource2 = getLocation(response); 645 } 646 final var condition = FEDORA_ID + "=" + prefix + "*"; 647 648 final String searchUrl = 649 getSearchEndpoint() + "include_total_result_count=true&condition=" + encode(condition) + 650 "&max_results=2"; 651 try (final var response = execute(new HttpGet(searchUrl))) { 652 assertEquals(OK.getStatusCode(), getStatus(response)); 653 final SearchResult result = objectMapper.readValue(response.getEntity().getContent(), SearchResult.class); 654 assertEquals(1, result.getItems().size()); 655 assertEquals(1, result.getPagination().getTotalResults()); 656 assertEquals(resource1, result.getItems().get(0).get("fedora_id")); 657 } 658 } 659}