From 6fcd557be3586c5786c231c292c4c7c26190b87e Mon Sep 17 00:00:00 2001 From: psmagin Date: Wed, 11 Dec 2024 18:57:37 +0200 Subject: [PATCH] feat(cn-browse): Implement browsing endpoint for Call Numbers Closes: MSEARCH-865 --- NEWS.md | 1 + descriptors/ModuleDescriptor-template.json | 19 +- .../folio/search/configuration/WebConfig.java | 2 +- .../search/controller/BrowseController.java | 53 +- .../search/controller/FacetsController.java | 3 +- .../search/model/service/BrowseContext.java | 9 - .../AbstractBrowseServiceBySearchAfter.java | 2 +- ...elvingOrderBrowseServiceBySearchAfter.java | 128 ++++ .../CallNumberBrowseResultConverter.java | 55 +- .../browse/CallNumberBrowseService.java | 218 ++----- .../browse/ClassificationBrowseService.java | 101 +-- .../browse/LegacyCallNumberBrowseService.java | 202 ++++++ .../consortium/ConsortiumSearchHelper.java | 1 + .../impl/CallNumberResourceExtractor.java | 12 +- .../folio/search/utils/CallNumberUtils.java | 10 +- .../org/folio/search/utils/SearchUtils.java | 10 +- .../resources/model/instance_call_number.json | 6 +- .../resources/swagger.api/mod-search.yaml | 3 + ...instance-call-number-browse-cql-query.yaml | 49 ++ .../browse-call-numbers-instances.yaml | 4 +- .../browse-instance-call-numbers.yaml | 27 + .../browse-instance-classifications.yaml | 4 +- .../schemas/entity/recordType.yaml | 1 + .../legacy/legacyCallNumberBrowseItem.yaml | 17 + .../legacy/legacyCallNumberBrowseResult.yaml | 17 + .../response/callNumberBrowseItem.yaml | 32 +- .../controller/BrowseCallNumberAroundIT.java | 10 +- .../BrowseCallNumberConsortiumIT.java | 168 ++--- .../search/controller/BrowseCallNumberIT.java | 598 ++++++------------ .../controller/BrowseCallNumberOtherIT.java | 74 +-- .../controller/BrowseCallNumberTypedIT.java | 40 +- ...owseCallNumberTypedIrrelevantResultIT.java | 16 +- .../controller/BrowseControllerTest.java | 119 +++- .../browse/BrowseContextProviderTest.java | 49 +- .../CallNumberBrowseResultConverterTest.java | 19 +- ...=> LegacyCallNumberBrowseServiceTest.java} | 101 ++- .../CallNumberResourceExtractorTest.java | 5 +- .../search/support/base/ApiEndpoints.java | 4 + .../base/BaseConsortiumIntegrationTest.java | 15 + .../search/utils/CallNumberTestData.java | 190 ++++++ .../search/utils/CallNumberUtilsTest.java | 206 ------ .../org/folio/search/utils/TestUtils.java | 64 +- .../samples/cn-browse/call-numbers.csv | 101 +++ .../cn-browse/instances-with-call-numbers.csv | 51 ++ .../resources/samples/cn-browse/locations.csv | 4 + 45 files changed, 1559 insertions(+), 1261 deletions(-) create mode 100644 src/main/java/org/folio/search/service/browse/AbstractShelvingOrderBrowseServiceBySearchAfter.java create mode 100644 src/main/java/org/folio/search/service/browse/LegacyCallNumberBrowseService.java create mode 100644 src/main/resources/swagger.api/parameters/instance-call-number-browse-cql-query.yaml create mode 100644 src/main/resources/swagger.api/paths/browse-call-numbers/browse-instance-call-numbers.yaml create mode 100644 src/main/resources/swagger.api/schemas/legacy/legacyCallNumberBrowseItem.yaml create mode 100644 src/main/resources/swagger.api/schemas/legacy/legacyCallNumberBrowseResult.yaml rename src/test/java/org/folio/search/service/browse/{CallNumberBrowseServiceTest.java => LegacyCallNumberBrowseServiceTest.java} (88%) create mode 100644 src/test/java/org/folio/search/utils/CallNumberTestData.java create mode 100644 src/test/resources/samples/cn-browse/call-numbers.csv create mode 100644 src/test/resources/samples/cn-browse/instances-with-call-numbers.csv create mode 100644 src/test/resources/samples/cn-browse/locations.csv diff --git a/NEWS.md b/NEWS.md index f97d5f680..23a5f2431 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,7 @@ * Call Numbers Browse: Implement Database Structure and Logic for Managing Call Numbers ([MSEARCH-862](https://folio-org.atlassian.net/browse/MSEARCH-862)) * Call Numbers Browse: Implement Call Number Browse Config ([MSEARCH-863](https://folio-org.atlassian.net/browse/MSEARCH-863)) * Call Numbers Browse: Implement Indexing and Re-indexing Mechanisms for Call-Numbers ([MSEARCH-864](https://folio-org.atlassian.net/browse/MSEARCH-864)) +* Call Numbers Browse: Implement Browsing Endpoint for Call-Numbers ([MSEARCH-865](https://folio-org.atlassian.net/browse/MSEARCH-865)) ### Bug fixes * Remove shelving order calculation for local call-number types diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 40f337018..d2149cd99 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -386,7 +386,7 @@ }, { "id": "browse", - "version": "1.4", + "version": "1.5", "handlers": [ { "methods": [ @@ -400,6 +400,18 @@ "user-tenants.collection.get" ] }, + { + "methods": [ + "GET" + ], + "pathPattern": "/browse/call-numbers/{browseOptionId}/instances", + "permissionsRequired": [ + "browse.call-numbers-new.instances.collection.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] + }, { "methods": [ "GET" @@ -727,6 +739,11 @@ "displayName": "Browse - provides collections of browse items for instance by call number", "description": "Browse instances by given query" }, + { + "permissionName": "browse.call-numbers-new.instances.collection.get", + "displayName": "Browse - provides collections of browse items for instance by call number", + "description": "Browse instances by given query" + }, { "permissionName": "browse.classification-numbers.instances.collection.get", "displayName": "Browse - provides collections of browse by classification number", diff --git a/src/main/java/org/folio/search/configuration/WebConfig.java b/src/main/java/org/folio/search/configuration/WebConfig.java index ae4e822b8..93b1637bd 100644 --- a/src/main/java/org/folio/search/configuration/WebConfig.java +++ b/src/main/java/org/folio/search/configuration/WebConfig.java @@ -25,7 +25,7 @@ public void addFormatters(FormatterRegistry registry) { private static final class StringToRecordTypeEnumConverter implements Converter { @Override public RecordType convert(String source) { - return RecordType.valueOf(source.toUpperCase()); + return RecordType.valueOf(source.toUpperCase().replace('-', '_')); } } diff --git a/src/main/java/org/folio/search/controller/BrowseController.java b/src/main/java/org/folio/search/controller/BrowseController.java index 640911c34..0b32cdad0 100644 --- a/src/main/java/org/folio/search/controller/BrowseController.java +++ b/src/main/java/org/folio/search/controller/BrowseController.java @@ -3,6 +3,7 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import static org.folio.search.model.types.ResourceType.AUTHORITY; import static org.folio.search.model.types.ResourceType.INSTANCE; +import static org.folio.search.model.types.ResourceType.INSTANCE_CALL_NUMBER; import static org.folio.search.model.types.ResourceType.INSTANCE_CLASSIFICATION; import static org.folio.search.model.types.ResourceType.INSTANCE_CONTRIBUTOR; import static org.folio.search.model.types.ResourceType.INSTANCE_SUBJECT; @@ -10,6 +11,7 @@ import static org.folio.search.utils.SearchUtils.CALL_NUMBER_BROWSING_FIELD; import static org.folio.search.utils.SearchUtils.CLASSIFICATION_NUMBER_BROWSING_FIELD; import static org.folio.search.utils.SearchUtils.CONTRIBUTOR_BROWSING_FIELD; +import static org.folio.search.utils.SearchUtils.LEGACY_CALL_NUMBER_BROWSING_FIELD; import static org.folio.search.utils.SearchUtils.SHELVING_ORDER_BROWSING_FIELD; import static org.folio.search.utils.SearchUtils.SUBJECT_BROWSING_FIELD; import static org.folio.search.utils.SearchUtils.TYPED_CALL_NUMBER_BROWSING_FIELD; @@ -17,11 +19,13 @@ import lombok.RequiredArgsConstructor; import org.folio.search.domain.dto.AuthorityBrowseResult; import org.folio.search.domain.dto.BrowseOptionType; +import org.folio.search.domain.dto.CallNumberBrowseItem; import org.folio.search.domain.dto.CallNumberBrowseResult; import org.folio.search.domain.dto.CallNumberType; import org.folio.search.domain.dto.ClassificationNumberBrowseItem; import org.folio.search.domain.dto.ClassificationNumberBrowseResult; import org.folio.search.domain.dto.ContributorBrowseResult; +import org.folio.search.domain.dto.LegacyCallNumberBrowseResult; import org.folio.search.domain.dto.SubjectBrowseResult; import org.folio.search.exception.RequestValidationException; import org.folio.search.model.BrowseResult; @@ -32,6 +36,7 @@ import org.folio.search.service.browse.CallNumberBrowseService; import org.folio.search.service.browse.ClassificationBrowseService; import org.folio.search.service.browse.ContributorBrowseService; +import org.folio.search.service.browse.LegacyCallNumberBrowseService; import org.folio.search.service.browse.SubjectBrowseService; import org.folio.search.service.consortium.TenantProvider; import org.springframework.http.ResponseEntity; @@ -47,9 +52,10 @@ public class BrowseController implements BrowseApi { private final SubjectBrowseService subjectBrowseService; private final AuthorityBrowseService authorityBrowseService; - private final CallNumberBrowseService callNumberBrowseService; + private final LegacyCallNumberBrowseService legacyCallNumberBrowseService; private final ContributorBrowseService contributorBrowseService; private final ClassificationBrowseService classificationBrowseService; + private final CallNumberBrowseService callNumberBrowseService; private final TenantProvider tenantProvider; @Override @@ -67,20 +73,36 @@ public ResponseEntity browseAuthorities(String query, Str } @Override - public ResponseEntity browseInstancesByCallNumber(String query, String tenant, - Integer limit, Boolean expandAll, - Boolean highlightMatch, - Integer precedingRecordsCount, - CallNumberType callNumberType) { + public ResponseEntity browseInstancesByCallNumber(BrowseOptionType browseOptionId, + String query, String tenant, + Integer limit, Boolean highlightMatch, + Integer precedingRecordsCount) { + var browseRequest = getBrowseRequestBuilder(query, tenant, limit, false, highlightMatch, precedingRecordsCount) + .resource(INSTANCE_CALL_NUMBER) + .browseOptionType(browseOptionId) + .targetField(CALL_NUMBER_BROWSING_FIELD) + .build(); + + var browseResult = callNumberBrowseService.browse(browseRequest); + return ResponseEntity.ok(toCallNumberBrowseResultDto(browseResult)); + } + + @Override + public ResponseEntity browseInstancesByCallNumberLegacy(String query, String tenant, + Integer limit, + Boolean expandAll, + Boolean highlightMatch, + Integer precedingRecordsCount, + CallNumberType callNumberType) { var browseRequest = getBrowseRequestBuilder(query, tenant, limit, expandAll, highlightMatch, precedingRecordsCount) .resource(INSTANCE) .targetField(SHELVING_ORDER_BROWSING_FIELD) - .subField(callNumberType == null ? CALL_NUMBER_BROWSING_FIELD : TYPED_CALL_NUMBER_BROWSING_FIELD) + .subField(callNumberType == null ? LEGACY_CALL_NUMBER_BROWSING_FIELD : TYPED_CALL_NUMBER_BROWSING_FIELD) .refinedCondition(callNumberType != null ? callNumberType.getValue() : null) .build(); - var instanceByCallNumber = callNumberBrowseService.browse(browseRequest); - return ResponseEntity.ok(new CallNumberBrowseResult() + var instanceByCallNumber = legacyCallNumberBrowseService.browse(browseRequest); + return ResponseEntity.ok(new LegacyCallNumberBrowseResult() .items(instanceByCallNumber.getRecords()) .totalRecords(instanceByCallNumber.getTotalRecords()) .prev(instanceByCallNumber.getPrev()) @@ -99,7 +121,7 @@ public ResponseEntity browseInstancesByClassif .build(); var browseResult = classificationBrowseService.browse(browseRequest); - return ResponseEntity.ok(toBrowseResultDto(browseResult)); + return ResponseEntity.ok(toClassificationBrowseResultDto(browseResult)); } @Override @@ -132,7 +154,8 @@ public ResponseEntity browseInstancesBySubject(String query .next(browseResult.getNext())); } - private ClassificationNumberBrowseResult toBrowseResultDto(BrowseResult result) { + private ClassificationNumberBrowseResult toClassificationBrowseResultDto( + BrowseResult result) { return new ClassificationNumberBrowseResult() .totalRecords(result.getTotalRecords()) .items(result.getRecords()) @@ -140,6 +163,14 @@ private ClassificationNumberBrowseResult toBrowseResultDto(BrowseResult result) { + return new CallNumberBrowseResult() + .totalRecords(result.getTotalRecords()) + .items(result.getRecords()) + .prev(result.getPrev()) + .next(result.getNext()); + } + private BrowseRequestBuilder getBrowseRequestBuilder(String query, String tenant, Integer limit, Boolean expandAll, Boolean highlightMatch, Integer precedingRecordsCount) { diff --git a/src/main/java/org/folio/search/controller/FacetsController.java b/src/main/java/org/folio/search/controller/FacetsController.java index d796c8b18..2cc831ec6 100644 --- a/src/main/java/org/folio/search/controller/FacetsController.java +++ b/src/main/java/org/folio/search/controller/FacetsController.java @@ -26,7 +26,8 @@ public class FacetsController implements FacetsApi { RecordType.AUTHORITIES, ResourceType.AUTHORITY, RecordType.CONTRIBUTORS, ResourceType.INSTANCE_CONTRIBUTOR, RecordType.SUBJECTS, ResourceType.INSTANCE_SUBJECT, - RecordType.CLASSIFICATIONS, ResourceType.INSTANCE_CLASSIFICATION + RecordType.CLASSIFICATIONS, ResourceType.INSTANCE_CLASSIFICATION, + RecordType.CALL_NUMBERS, ResourceType.INSTANCE_CALL_NUMBER ); private final FacetService facetService; diff --git a/src/main/java/org/folio/search/model/service/BrowseContext.java b/src/main/java/org/folio/search/model/service/BrowseContext.java index 19c80ca24..c083612dd 100644 --- a/src/main/java/org/folio/search/model/service/BrowseContext.java +++ b/src/main/java/org/folio/search/model/service/BrowseContext.java @@ -53,15 +53,6 @@ public boolean isAnchorIncluded(boolean isForward) { return isForward ? this.succeedingQuery.includeLower() : this.precedingQuery.includeUpper(); } - /** - * Checks if anchor is included in the range query or not. - * - * @return {@code true} if anchor is included, {@code false} - otherwise - */ - public boolean isAnchorIncluded() { - return isAnchorIncluded(true); - } - /** * Returns limit for browsing. */ diff --git a/src/main/java/org/folio/search/service/browse/AbstractBrowseServiceBySearchAfter.java b/src/main/java/org/folio/search/service/browse/AbstractBrowseServiceBySearchAfter.java index b56e5a874..1f3895e2d 100644 --- a/src/main/java/org/folio/search/service/browse/AbstractBrowseServiceBySearchAfter.java +++ b/src/main/java/org/folio/search/service/browse/AbstractBrowseServiceBySearchAfter.java @@ -229,7 +229,7 @@ private void logBrowseRequest(BrowseRequest request, String functionName) { } private void logMultiSearchRequest(BrowseRequest request, int size) { - log.info("browseAround:: Attempting to multi-search request [tenant: {}, searchSource.size: {}]", + log.debug("browseAround:: Attempting to multi-search request [tenant: {}, searchSource.size: {}]", request.getTenantId(), size); } } diff --git a/src/main/java/org/folio/search/service/browse/AbstractShelvingOrderBrowseServiceBySearchAfter.java b/src/main/java/org/folio/search/service/browse/AbstractShelvingOrderBrowseServiceBySearchAfter.java new file mode 100644 index 000000000..fb62601d5 --- /dev/null +++ b/src/main/java/org/folio/search/service/browse/AbstractShelvingOrderBrowseServiceBySearchAfter.java @@ -0,0 +1,128 @@ +package org.folio.search.service.browse; + +import static java.util.Locale.ROOT; +import static org.folio.search.utils.SearchUtils.BROWSE_FIELDS_MAP; +import static org.folio.search.utils.SearchUtils.DEFAULT_SHELVING_ORDER_BROWSING_FIELD; +import static org.opensearch.index.query.QueryBuilders.boolQuery; +import static org.opensearch.index.query.QueryBuilders.matchAllQuery; +import static org.opensearch.index.query.QueryBuilders.termQuery; +import static org.opensearch.search.builder.SearchSourceBuilder.searchSource; +import static org.opensearch.search.sort.SortBuilders.fieldSort; +import static org.opensearch.search.sort.SortOrder.ASC; +import static org.opensearch.search.sort.SortOrder.DESC; + +import java.util.Set; +import java.util.function.Function; +import lombok.extern.log4j.Log4j2; +import org.folio.search.domain.dto.BrowseConfig; +import org.folio.search.domain.dto.BrowseType; +import org.folio.search.model.index.InstanceSubResource; +import org.folio.search.model.service.BrowseContext; +import org.folio.search.model.service.BrowseRequest; +import org.folio.search.service.consortium.BrowseConfigServiceDecorator; +import org.folio.search.service.consortium.ConsortiumSearchHelper; +import org.folio.search.utils.ShelvingOrderCalculationHelper; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.search.builder.SearchSourceBuilder; + +@Log4j2 +public abstract class AbstractShelvingOrderBrowseServiceBySearchAfter + extends AbstractBrowseServiceBySearchAfter { + + private final ConsortiumSearchHelper consortiumSearchHelper; + private final BrowseConfigServiceDecorator configService; + + protected AbstractShelvingOrderBrowseServiceBySearchAfter(ConsortiumSearchHelper consortiumSearchHelper, + BrowseConfigServiceDecorator configService) { + this.consortiumSearchHelper = consortiumSearchHelper; + this.configService = configService; + } + + @Override + protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest req, BrowseContext ctx) { + log.debug("getAnchorSearchQuery:: by [request: {}]", req); + var config = configService.getConfig(getBrowseType(), req.getBrowseOptionType()); + + var browseField = getBrowseField(config); + var termQueryBuilder = getQuery(ctx, config, termQuery(req.getTargetField(), ctx.getAnchor())); + var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, termQueryBuilder, req.getResource()); + var sortOrder = ctx.isBrowsingForward() ? ASC : DESC; + return searchSource().query(query) + .sort(fieldSort(browseField).order(sortOrder)) + .sort(fieldSort(req.getTargetField()).order(sortOrder)) + .size(ctx.getLimit(ctx.isBrowsingForward())) + .from(0); + } + + @Override + protected SearchSourceBuilder getSearchQuery(BrowseRequest req, BrowseContext ctx, boolean isBrowsingForward) { + log.debug("getSearchQuery:: by [request: {}, isBrowsingForward: {}]", req, isBrowsingForward); + var config = configService.getConfig(getBrowseType(), req.getBrowseOptionType()); + + var browseField = getBrowseField(config); + var normalizedAnchor = ShelvingOrderCalculationHelper.calculate(ctx.getAnchor(), config.getShelvingAlgorithm()); + var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, getQuery(ctx, config, null), + req.getResource()); + + var sortOrder = isBrowsingForward ? ASC : DESC; + return searchSource().query(query) + .searchAfter(new Object[] {normalizedAnchor.toLowerCase(ROOT), ctx.getAnchor().toLowerCase(ROOT)}) + .sort(fieldSort(browseField).order(sortOrder)) + .sort(fieldSort(req.getTargetField()).order(sortOrder)) + .size(ctx.getLimit(isBrowsingForward) + 1) + .from(0); + } + + /** + * Returns the type of browsing supported by this service. + * + * @return the {@link BrowseType} for this service + */ + protected abstract BrowseType getBrowseType(); + + /** + * Returns the field name used for type identification in the browse service. + * + * @return the type ID field name + */ + protected abstract String getTypeIdField(); + + protected Integer getTotalRecords(BrowseContext ctx, R resource, Function> func) { + return consortiumSearchHelper.filterSubResourcesForConsortium(ctx, resource, func) + .stream() + .map(InstanceSubResource::getCount) + .reduce(0, Integer::sum); + } + + private QueryBuilder getQuery(BrowseContext ctx, BrowseConfig config, TermQueryBuilder anchorQuery) { + var typeIds = config.getTypeIds(); + if (config.getTypeIds().isEmpty() && ctx.getFilters().isEmpty()) { + if (anchorQuery != null) { + return anchorQuery; + } + return matchAllQuery(); + } else { + var boolQueryMain = boolQuery(); + if (!config.getTypeIds().isEmpty()) { + var boolQuery = boolQuery(); + for (var typeId : typeIds) { + boolQuery.should(termQuery(getTypeIdField(), typeId.toString())); + } + boolQueryMain.must(boolQuery); + } + if (!ctx.getFilters().isEmpty()) { + ctx.getFilters().forEach(boolQueryMain::filter); + } + if (anchorQuery != null) { + boolQueryMain.must(anchorQuery); + } + return boolQueryMain; + } + } + + private static String getBrowseField(BrowseConfig config) { + return BROWSE_FIELDS_MAP.getOrDefault(config.getShelvingAlgorithm(), DEFAULT_SHELVING_ORDER_BROWSING_FIELD); + } + +} diff --git a/src/main/java/org/folio/search/service/browse/CallNumberBrowseResultConverter.java b/src/main/java/org/folio/search/service/browse/CallNumberBrowseResultConverter.java index 38ae3d059..2adc13ddc 100644 --- a/src/main/java/org/folio/search/service/browse/CallNumberBrowseResultConverter.java +++ b/src/main/java/org/folio/search/service/browse/CallNumberBrowseResultConverter.java @@ -24,9 +24,9 @@ import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.folio.search.domain.dto.CallNumberBrowseItem; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.Item; +import org.folio.search.domain.dto.LegacyCallNumberBrowseItem; import org.folio.search.model.BrowseResult; import org.folio.search.model.SearchResult; import org.folio.search.model.service.BrowseContext; @@ -53,10 +53,10 @@ public class CallNumberBrowseResultConverter { * @param ctx - {@link BrowseContext} value * @param request - initial request * @param isBrowsingForward - direction of browsing - * @return converted {@link SearchResult} object with {@link CallNumberBrowseItem} values + * @return converted {@link SearchResult} object with {@link LegacyCallNumberBrowseItem} values */ - public BrowseResult convert(SearchResponse resp, BrowseContext ctx, BrowseRequest request, - boolean isBrowsingForward) { + public BrowseResult convert(SearchResponse resp, BrowseContext ctx, BrowseRequest request, + boolean isBrowsingForward) { var searchResult = documentConverter.convertToSearchResult(resp, Instance.class, this::mapToBrowseItem); var browseResult = BrowseResult.of(searchResult); var browseItems = browseResult.getRecords(); @@ -76,33 +76,35 @@ public BrowseResult convert(SearchResponse resp, BrowseCon return browseResult.records(collapseCallNumberBrowseItems(populatedItems)); } - private CallNumberBrowseItem mapToBrowseItem(SearchHit searchHit, Instance instance) { + private LegacyCallNumberBrowseItem mapToBrowseItem(SearchHit searchHit, Instance instance) { var shelfKey = (String) searchHit.getSortValues()[0]; - return new CallNumberBrowseItem().totalRecords(1).instance(instance).shelfKey(shelfKey); + return new LegacyCallNumberBrowseItem().totalRecords(1).instance(instance).shelfKey(shelfKey); } - private static List populateItemsWithIntermediateResults(List browseItems, - BrowseContext ctx, - boolean removeDuplicates, - String typeId, - boolean isBrowsingForward) { + private static List populateItemsWithIntermediateResults( + List browseItems, + BrowseContext ctx, + boolean removeDuplicates, + String typeId, + boolean isBrowsingForward) { return browseItems.stream() .map(item -> getCallNumberBrowseItemsBetween(item, typeId, removeDuplicates)) .flatMap(Collection::stream) .filter(browseItem -> isValidBrowseItem(browseItem, ctx, isBrowsingForward)) - .sorted(comparing(CallNumberBrowseItem::getShelfKey)) + .sorted(comparing(LegacyCallNumberBrowseItem::getShelfKey)) .toList(); } - private static List fillItemsWithFullCallNumbers( - List items, BrowseContext ctx, boolean isBrowsingForward) { + private static List fillItemsWithFullCallNumbers( + List items, BrowseContext ctx, boolean isBrowsingForward) { return items.stream() .filter(item -> isValidBrowseItem(item, ctx, isBrowsingForward)) .map(browseItem -> browseItem.fullCallNumber(getFullCallNumber(browseItem))) .toList(); } - private static boolean isValidBrowseItem(CallNumberBrowseItem item, BrowseContext ctx, boolean isBrowsingForward) { + private static boolean isValidBrowseItem(LegacyCallNumberBrowseItem item, BrowseContext ctx, + boolean isBrowsingForward) { var comparisonResult = item.getShelfKey().compareTo(ctx.getAnchor()); if (comparisonResult == 0 && ctx.isAnchorIncluded(isBrowsingForward)) { return true; @@ -111,8 +113,9 @@ private static boolean isValidBrowseItem(CallNumberBrowseItem item, BrowseContex return isBrowsingForward ? comparisonResult > 0 : comparisonResult < 0; } - private static List getCallNumberBrowseItemsBetween(CallNumberBrowseItem browseItem, - String typeId, boolean removeDuplicates) { + private static List getCallNumberBrowseItemsBetween(LegacyCallNumberBrowseItem browseItem, + String typeId, + boolean removeDuplicates) { var itemsByShelfKeys = toStreamSafe(browseItem.getInstance().getItems()) .filter(item -> StringUtils.isNotBlank(item.getEffectiveShelvingOrder())) .collect(groupingBy(item -> toRootUpperCase(item.getEffectiveShelvingOrder()), LinkedHashMap::new, toList())); @@ -126,21 +129,22 @@ private static List getCallNumberBrowseItemsBetween(CallNu .map(shelfKey -> mapToCallNumberBrowseItem(browseItem, shelfKey, findFirst(itemsByShelfKeys.get(shelfKey)))); if (removeDuplicates) { - callNumbersStream = callNumbersStream.filter(distinctByKey(CallNumberBrowseItem::getFullCallNumber)); + callNumbersStream = callNumbersStream.filter(distinctByKey(LegacyCallNumberBrowseItem::getFullCallNumber)); } return callNumbersStream.toList(); } - private static CallNumberBrowseItem mapToCallNumberBrowseItem(CallNumberBrowseItem browseItem, String shelfKey, - Optional optionalOfItem) { - return new CallNumberBrowseItem() + private static LegacyCallNumberBrowseItem mapToCallNumberBrowseItem(LegacyCallNumberBrowseItem browseItem, + String shelfKey, + Optional optionalOfItem) { + return new LegacyCallNumberBrowseItem() .shelfKey(normalizeEffectiveShelvingOrder(shelfKey)) .fullCallNumber(getFullCallNumber(optionalOfItem)) .instance(browseItem.getInstance()) .totalRecords(1); } - private static String getFullCallNumber(CallNumberBrowseItem browseItem) { + private static String getFullCallNumber(LegacyCallNumberBrowseItem browseItem) { return getFullCallNumber( Optional.ofNullable(browseItem.getInstance()) .map(Instance::getItems) @@ -158,17 +162,18 @@ private static String getFullCallNumber(Optional optionalOfItem) { .orElse(null); } - private static List collapseCallNumberBrowseItems(List items) { + private static List collapseCallNumberBrowseItems( + List items) { if (items.isEmpty()) { return emptyList(); } - var collapsedItems = new ArrayList(); + var collapsedItems = new ArrayList(); var iterator = items.iterator(); var prevItem = iterator.next(); collapsedItems.add(prevItem); - CallNumberBrowseItem currItem; + LegacyCallNumberBrowseItem currItem; while (iterator.hasNext()) { currItem = iterator.next(); if (Objects.equals(prevItem.getShelfKey(), currItem.getShelfKey())) { diff --git a/src/main/java/org/folio/search/service/browse/CallNumberBrowseService.java b/src/main/java/org/folio/search/service/browse/CallNumberBrowseService.java index 2e5a2eb37..9645e37cc 100644 --- a/src/main/java/org/folio/search/service/browse/CallNumberBrowseService.java +++ b/src/main/java/org/folio/search/service/browse/CallNumberBrowseService.java @@ -1,204 +1,68 @@ package org.folio.search.service.browse; -import static java.lang.Boolean.TRUE; -import static java.util.Collections.singletonList; -import static org.apache.commons.collections4.CollectionUtils.isEmpty; -import static org.folio.search.model.types.CallNumberTypeSource.FOLIO; -import static org.folio.search.utils.CollectionUtils.mergeSafelyToList; +import static org.folio.search.utils.SearchUtils.CALL_NUMBER_TYPE_ID_FIELD; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.StringUtils; -import org.folio.search.cql.CqlSearchQueryConverter; +import org.folio.search.domain.dto.BrowseType; import org.folio.search.domain.dto.CallNumberBrowseItem; import org.folio.search.model.BrowseResult; +import org.folio.search.model.SearchResult; +import org.folio.search.model.index.CallNumberResource; import org.folio.search.model.service.BrowseContext; -import org.folio.search.model.service.BrowseRequest; -import org.folio.search.repository.SearchRepository; -import org.opensearch.search.builder.SearchSourceBuilder; -import org.opensearch.search.sort.SortOrder; +import org.folio.search.service.consortium.BrowseConfigServiceDecorator; +import org.folio.search.service.consortium.ConsortiumSearchHelper; import org.springframework.stereotype.Service; @Log4j2 @Service -@RequiredArgsConstructor -public class CallNumberBrowseService extends AbstractBrowseService { - - public static final List FOLIO_CALL_NUMBER_TYPES_SOURCES = Collections.singletonList(FOLIO.getSource()); - private static final int ADDITIONAL_REQUEST_SIZE = 100; - private final SearchRepository searchRepository; - private final CqlSearchQueryConverter cqlSearchQueryConverter; - private final CallNumberBrowseQueryProvider callNumberBrowseQueryProvider; - private final CallNumberBrowseResultConverter callNumberBrowseResultConverter; - - @Override - protected BrowseResult browseInOneDirection(BrowseRequest request, BrowseContext context) { - var isBrowsingForward = context.isBrowsingForward(); - var searchSource = callNumberBrowseQueryProvider.get(request, context, isBrowsingForward); - var searchResponse = searchRepository.search(request, searchSource); - var browseResult = callNumberBrowseResultConverter.convert(searchResponse, context, request, isBrowsingForward); - var records = browseResult.getRecords(); - var browseItems = trim(records, context, isBrowsingForward); - if (browseItems.isEmpty()) { - return new BrowseResult() - .records(browseItems) - .totalRecords(browseResult.getTotalRecords()); - } - - var callNumberBrowseItemFirst = browseItems.get(0); - searchSource.sorts().clear(); - searchSource.searchAfter(new Object[]{callNumberBrowseItemFirst.getShelfKey()}) - .sort("itemEffectiveShelvingOrder", SortOrder.DESC) - .from(0) - .size(5); - String prev = null; - var precedingResponse = searchRepository.search(request, searchSource); - if (precedingResponse.getHits() != null - && precedingResponse.getHits().getTotalHits() != null - && precedingResponse.getHits().getTotalHits().value > 0) { - prev = callNumberBrowseItemFirst.getShelfKey(); - } - searchSource.sorts().clear(); - var callNumberBrowseItemLast = browseItems.get(browseItems.size() - 1); - searchSource.searchAfter(new Object[]{callNumberBrowseItemLast.getShelfKey()}) - .sort("itemEffectiveShelvingOrder", SortOrder.ASC) - .from(0) - .size(5); - String next = null; - var succedingResponse = searchRepository.search(request, searchSource); - if (succedingResponse.getHits() != null - && succedingResponse.getHits().getTotalHits() != null - && succedingResponse.getHits().getTotalHits().value > 0) { - next = callNumberBrowseItemLast.getShelfKey(); - } - return new BrowseResult() - .records(browseItems) - .totalRecords(browseResult.getTotalRecords()) - .prev(prev) - .next(next); - } - - @Override - protected BrowseResult browseAround(BrowseRequest request, BrowseContext context) { - var precedingQuery = callNumberBrowseQueryProvider.get(request, context, false); - var succeedingQuery = callNumberBrowseQueryProvider.get(request, context, true); - var multiSearchResponse = searchRepository.msearch(request, List.of(precedingQuery, succeedingQuery)); - - var responses = multiSearchResponse.getResponses(); - var precedingResult = callNumberBrowseResultConverter.convert(responses[0].getResponse(), context, request, false); - var succeedingResult = callNumberBrowseResultConverter.convert(responses[1].getResponse(), context, request, true); - - if (precedingResult.getRecords().isEmpty() && precedingResult.getTotalRecords() > 0) { - log.debug("browseAround:: preceding result are empty: Do additional requests"); - precedingResult = additionalPrecedingRequests(request, context, precedingQuery); - } - - if (TRUE.equals(request.getHighlightMatch())) { - var callNumber = cqlSearchQueryConverter.convertToTermNode(request.getQuery(), request.getResource()).getTerm(); - highlightMatchingCallNumber(context, callNumber, succeedingResult); - } - - var browseItems = mergeSafelyToList( - trim(precedingResult.getRecords(), context, false), - trim(succeedingResult.getRecords(), context, true)); - - if (browseItems.isEmpty()) { - return new BrowseResult() - .records(browseItems) - .totalRecords(browseItems.size()); - } - - String prev = null; - var callNumberBrowseItemFirst = browseItems.get(0); - precedingQuery.searchAfter(new Object[]{callNumberBrowseItemFirst.getShelfKey()}) - .from(0).size(5); - var precedingResponse = searchRepository.search(request, precedingQuery); - if (precedingResponse.getHits() != null - && precedingResponse.getHits().getTotalHits() != null - && precedingResponse.getHits().getTotalHits().value > 0) { - prev = callNumberBrowseItemFirst.getShelfKey(); - } - String next = null; - var callNumberBrowseItemLast = browseItems.get(browseItems.size() - 1); - succeedingQuery.searchAfter(new Object[]{callNumberBrowseItemLast.getShelfKey()}) - .from(0).size(5); - var succeedingResponse = searchRepository.search(request, succeedingQuery); - if (succeedingResponse.getHits() != null - && succeedingResponse.getHits().getTotalHits() != null - && succeedingResponse.getHits().getTotalHits().value > 0) { - next = callNumberBrowseItemLast.getShelfKey(); - } - - return new BrowseResult() - .totalRecords(precedingResult.getTotalRecords() + succeedingResult.getTotalRecords()) - .prev(prev) - .next(next) - .records(browseItems); +public class CallNumberBrowseService + extends AbstractShelvingOrderBrowseServiceBySearchAfter { + protected CallNumberBrowseService(ConsortiumSearchHelper consortiumSearchHelper, + BrowseConfigServiceDecorator configService) { + super(consortiumSearchHelper, configService); } @Override protected String getValueForBrowsing(CallNumberBrowseItem browseItem) { - return browseItem.getShelfKey(); + return browseItem.getFullCallNumber(); } - private BrowseResult additionalPrecedingRequests(BrowseRequest request, - BrowseContext context, - SearchSourceBuilder precedingQuery) { - BrowseResult precedingResult = BrowseResult.empty(); - precedingQuery.size(ADDITIONAL_REQUEST_SIZE); - - while (precedingResult.getRecords().isEmpty()) { - int offset = precedingQuery.from() + precedingQuery.size(); - int size = precedingQuery.size() * 2; - if (offset + size >= 10000) { - break; - } - log.debug("additionalPrecedingRequests:: request offset {}, size {}", offset, size); - precedingQuery.from(offset).size(size); - - var searchResponse = searchRepository.search(request, precedingQuery); - var totalHits = searchResponse.getHits().getTotalHits(); - if (totalHits == null || totalHits.value == 0) { - log.debug("additionalPrecedingRequests:: response have no records"); - break; - } - precedingResult = callNumberBrowseResultConverter.convert(searchResponse, context, request, false); - } - return precedingResult; + @Override + protected BrowseType getBrowseType() { + return BrowseType.CALL_NUMBER; } - private static void highlightMatchingCallNumber(BrowseContext ctx, - String callNumber, - BrowseResult result) { - var items = result.getRecords(); - var anchor = ctx.getAnchor(); - - if (isEmpty(items)) { - result.setRecords(singletonList(getEmptyCallNumberBrowseItem(callNumber, anchor))); - return; - } - - var firstBrowseItem = items.get(0); - if (!StringUtils.equals(firstBrowseItem.getShelfKey(), anchor)) { - var browseItemsWithEmptyValue = new ArrayList(); - browseItemsWithEmptyValue.add(getEmptyCallNumberBrowseItem(callNumber, anchor)); - browseItemsWithEmptyValue.addAll(items); - result.setRecords(browseItemsWithEmptyValue); - return; - } - - firstBrowseItem.setIsAnchor(true); + @Override + protected String getTypeIdField() { + return CALL_NUMBER_TYPE_ID_FIELD; } - private static CallNumberBrowseItem getEmptyCallNumberBrowseItem(String callNumber, String shelfKey) { + @Override + protected CallNumberBrowseItem getEmptyBrowseItem(BrowseContext context) { return new CallNumberBrowseItem() - .fullCallNumber(callNumber) - .shelfKey(shelfKey) + .fullCallNumber(context.getAnchor()) .totalRecords(0) .isAnchor(true); } + + @Override + protected BrowseResult mapToBrowseResult(BrowseContext ctx, + SearchResult res, + boolean isAnchor) { + return BrowseResult.of(res) + .map(resource -> new CallNumberBrowseItem() + .fullCallNumber(resource.fullCallNumber()) + .callNumber(resource.callNumber()) + .callNumberPrefix(resource.callNumberPrefix()) + .callNumberSuffix(resource.callNumberSuffix()) + .callNumberTypeId(resource.callNumberTypeId()) + .volume(resource.volume()) + .chronology(resource.chronology()) + .enumeration(resource.enumeration()) + .copyNumber(resource.copyNumber()) + .isAnchor(isAnchor ? true : null) + .totalRecords(getTotalRecords(ctx, resource, CallNumberResource::instances))); + } + } diff --git a/src/main/java/org/folio/search/service/browse/ClassificationBrowseService.java b/src/main/java/org/folio/search/service/browse/ClassificationBrowseService.java index 0cac89c9b..58eeb382c 100644 --- a/src/main/java/org/folio/search/service/browse/ClassificationBrowseService.java +++ b/src/main/java/org/folio/search/service/browse/ClassificationBrowseService.java @@ -1,46 +1,27 @@ package org.folio.search.service.browse; -import static java.util.Locale.ROOT; -import static org.folio.search.utils.SearchUtils.BROWSE_FIELDS_MAP; import static org.folio.search.utils.SearchUtils.CLASSIFICATION_TYPE_ID_FIELD; -import static org.folio.search.utils.SearchUtils.DEFAULT_SHELVING_ORDER_BROWSING_FIELD; -import static org.opensearch.index.query.QueryBuilders.boolQuery; -import static org.opensearch.index.query.QueryBuilders.matchAllQuery; -import static org.opensearch.index.query.QueryBuilders.termQuery; -import static org.opensearch.search.builder.SearchSourceBuilder.searchSource; -import static org.opensearch.search.sort.SortBuilders.fieldSort; -import static org.opensearch.search.sort.SortOrder.ASC; -import static org.opensearch.search.sort.SortOrder.DESC; -import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.folio.search.domain.dto.BrowseConfig; import org.folio.search.domain.dto.BrowseType; import org.folio.search.domain.dto.ClassificationNumberBrowseItem; import org.folio.search.model.BrowseResult; import org.folio.search.model.SearchResult; import org.folio.search.model.index.ClassificationResource; -import org.folio.search.model.index.InstanceSubResource; import org.folio.search.model.service.BrowseContext; -import org.folio.search.model.service.BrowseRequest; import org.folio.search.service.consortium.BrowseConfigServiceDecorator; import org.folio.search.service.consortium.ConsortiumSearchHelper; -import org.folio.search.utils.ShelvingOrderCalculationHelper; -import org.opensearch.index.query.QueryBuilder; -import org.opensearch.index.query.TermQueryBuilder; -import org.opensearch.search.builder.SearchSourceBuilder; import org.springframework.stereotype.Service; @Log4j2 @Service -@RequiredArgsConstructor public class ClassificationBrowseService - extends AbstractBrowseServiceBySearchAfter { + extends AbstractShelvingOrderBrowseServiceBySearchAfter { - private static final String CLASSIFICATION_NUMBER_FIELD = "number"; - - private final ConsortiumSearchHelper consortiumSearchHelper; - private final BrowseConfigServiceDecorator configService; + protected ClassificationBrowseService(ConsortiumSearchHelper consortiumSearchHelper, + BrowseConfigServiceDecorator configService) { + super(consortiumSearchHelper, configService); + } @Override protected String getValueForBrowsing(ClassificationNumberBrowseItem browseItem) { @@ -48,38 +29,13 @@ protected String getValueForBrowsing(ClassificationNumberBrowseItem browseItem) } @Override - protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest req, BrowseContext ctx) { - log.debug("getAnchorSearchQuery:: by [request: {}]", req); - var config = configService.getConfig(BrowseType.CLASSIFICATION, req.getBrowseOptionType()); - - var browseField = getBrowseField(config); - var termQueryBuilder = getQuery(ctx, config, termQuery(req.getTargetField(), ctx.getAnchor())); - var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, termQueryBuilder, req.getResource()); - var sortOrder = ctx.isBrowsingForward() ? ASC : DESC; - return searchSource().query(query) - .sort(fieldSort(browseField).order(sortOrder)) - .sort(fieldSort(CLASSIFICATION_NUMBER_FIELD).order(sortOrder)) - .size(ctx.getLimit(ctx.isBrowsingForward())) - .from(0); + protected BrowseType getBrowseType() { + return BrowseType.CLASSIFICATION; } @Override - protected SearchSourceBuilder getSearchQuery(BrowseRequest req, BrowseContext ctx, boolean isBrowsingForward) { - log.debug("getSearchQuery:: by [request: {}, isBrowsingForward: {}]", req, isBrowsingForward); - var config = configService.getConfig(BrowseType.CLASSIFICATION, req.getBrowseOptionType()); - - var browseField = getBrowseField(config); - var normalizedAnchor = ShelvingOrderCalculationHelper.calculate(ctx.getAnchor(), config.getShelvingAlgorithm()); - var query = consortiumSearchHelper.filterBrowseQueryForActiveAffiliation(ctx, getQuery(ctx, config, null), - req.getResource()); - - var sortOrder = isBrowsingForward ? ASC : DESC; - return searchSource().query(query) - .searchAfter(new Object[] {normalizedAnchor.toLowerCase(ROOT), ctx.getAnchor().toLowerCase(ROOT)}) - .sort(fieldSort(browseField).order(sortOrder)) - .sort(fieldSort(CLASSIFICATION_NUMBER_FIELD).order(sortOrder)) - .size(ctx.getLimit(isBrowsingForward) + 1) - .from(0); + protected String getTypeIdField() { + return CLASSIFICATION_TYPE_ID_FIELD; } @Override @@ -99,44 +55,7 @@ protected BrowseResult mapToBrowseResult(BrowseC .classificationNumber(resource.number()) .classificationTypeId(resource.typeId()) .isAnchor(isAnchor ? true : null) - .totalRecords(getTotalRecords(ctx, resource))); - } - - private static QueryBuilder getQuery(BrowseContext ctx, BrowseConfig config, TermQueryBuilder anchorQuery) { - var typeIds = config.getTypeIds(); - if (config.getTypeIds().isEmpty() && ctx.getFilters().isEmpty()) { - if (anchorQuery != null) { - return anchorQuery; - } - return matchAllQuery(); - } else { - var boolQueryMain = boolQuery(); - if (!config.getTypeIds().isEmpty()) { - var boolQuery = boolQuery(); - for (var typeId : typeIds) { - boolQuery.should(termQuery(CLASSIFICATION_TYPE_ID_FIELD, typeId.toString())); - } - boolQueryMain.must(boolQuery); - } - if (!ctx.getFilters().isEmpty()) { - ctx.getFilters().forEach(boolQueryMain::filter); - } - if (anchorQuery != null) { - boolQueryMain.must(anchorQuery); - } - return boolQueryMain; - } - } - - private static String getBrowseField(BrowseConfig config) { - return BROWSE_FIELDS_MAP.getOrDefault(config.getShelvingAlgorithm(), DEFAULT_SHELVING_ORDER_BROWSING_FIELD); - } - - private Integer getTotalRecords(BrowseContext ctx, ClassificationResource classificationResource) { - return consortiumSearchHelper.filterSubResourcesForConsortium(ctx, classificationResource, - ClassificationResource::instances).stream() - .map(InstanceSubResource::getCount) - .reduce(0, Integer::sum); + .totalRecords(getTotalRecords(ctx, resource, ClassificationResource::instances))); } } diff --git a/src/main/java/org/folio/search/service/browse/LegacyCallNumberBrowseService.java b/src/main/java/org/folio/search/service/browse/LegacyCallNumberBrowseService.java new file mode 100644 index 000000000..877420f10 --- /dev/null +++ b/src/main/java/org/folio/search/service/browse/LegacyCallNumberBrowseService.java @@ -0,0 +1,202 @@ +package org.folio.search.service.browse; + +import static java.lang.Boolean.TRUE; +import static java.util.Collections.singletonList; +import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import static org.folio.search.utils.CollectionUtils.mergeSafelyToList; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.folio.search.cql.CqlSearchQueryConverter; +import org.folio.search.domain.dto.LegacyCallNumberBrowseItem; +import org.folio.search.model.BrowseResult; +import org.folio.search.model.service.BrowseContext; +import org.folio.search.model.service.BrowseRequest; +import org.folio.search.repository.SearchRepository; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.search.sort.SortOrder; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class LegacyCallNumberBrowseService extends AbstractBrowseService { + + private static final int ADDITIONAL_REQUEST_SIZE = 100; + private final SearchRepository searchRepository; + private final CqlSearchQueryConverter cqlSearchQueryConverter; + private final CallNumberBrowseQueryProvider callNumberBrowseQueryProvider; + private final CallNumberBrowseResultConverter callNumberBrowseResultConverter; + + @Override + protected BrowseResult browseInOneDirection(BrowseRequest request, + BrowseContext context) { + var isBrowsingForward = context.isBrowsingForward(); + var searchSource = callNumberBrowseQueryProvider.get(request, context, isBrowsingForward); + var searchResponse = searchRepository.search(request, searchSource); + var browseResult = callNumberBrowseResultConverter.convert(searchResponse, context, request, isBrowsingForward); + var records = browseResult.getRecords(); + var browseItems = trim(records, context, isBrowsingForward); + if (browseItems.isEmpty()) { + return new BrowseResult() + .records(browseItems) + .totalRecords(browseResult.getTotalRecords()); + } + + var callNumberBrowseItemFirst = browseItems.get(0); + searchSource.sorts().clear(); + searchSource.searchAfter(new Object[]{callNumberBrowseItemFirst.getShelfKey()}) + .sort("itemEffectiveShelvingOrder", SortOrder.DESC) + .from(0) + .size(5); + String prev = null; + var precedingResponse = searchRepository.search(request, searchSource); + if (precedingResponse.getHits() != null + && precedingResponse.getHits().getTotalHits() != null + && precedingResponse.getHits().getTotalHits().value > 0) { + prev = callNumberBrowseItemFirst.getShelfKey(); + } + searchSource.sorts().clear(); + var callNumberBrowseItemLast = browseItems.get(browseItems.size() - 1); + searchSource.searchAfter(new Object[]{callNumberBrowseItemLast.getShelfKey()}) + .sort("itemEffectiveShelvingOrder", SortOrder.ASC) + .from(0) + .size(5); + String next = null; + var succedingResponse = searchRepository.search(request, searchSource); + if (succedingResponse.getHits() != null + && succedingResponse.getHits().getTotalHits() != null + && succedingResponse.getHits().getTotalHits().value > 0) { + next = callNumberBrowseItemLast.getShelfKey(); + } + return new BrowseResult() + .records(browseItems) + .totalRecords(browseResult.getTotalRecords()) + .prev(prev) + .next(next); + } + + @Override + protected BrowseResult browseAround(BrowseRequest request, BrowseContext context) { + var precedingQuery = callNumberBrowseQueryProvider.get(request, context, false); + var succeedingQuery = callNumberBrowseQueryProvider.get(request, context, true); + var multiSearchResponse = searchRepository.msearch(request, List.of(precedingQuery, succeedingQuery)); + + var responses = multiSearchResponse.getResponses(); + var precedingResult = callNumberBrowseResultConverter.convert(responses[0].getResponse(), context, request, false); + var succeedingResult = callNumberBrowseResultConverter.convert(responses[1].getResponse(), context, request, true); + + if (precedingResult.getRecords().isEmpty() && precedingResult.getTotalRecords() > 0) { + log.debug("browseAround:: preceding result are empty: Do additional requests"); + precedingResult = additionalPrecedingRequests(request, context, precedingQuery); + } + + if (TRUE.equals(request.getHighlightMatch())) { + var callNumber = cqlSearchQueryConverter.convertToTermNode(request.getQuery(), request.getResource()).getTerm(); + highlightMatchingCallNumber(context, callNumber, succeedingResult); + } + + var browseItems = mergeSafelyToList( + trim(precedingResult.getRecords(), context, false), + trim(succeedingResult.getRecords(), context, true)); + + if (browseItems.isEmpty()) { + return new BrowseResult() + .records(browseItems) + .totalRecords(browseItems.size()); + } + + String prev = null; + var callNumberBrowseItemFirst = browseItems.get(0); + precedingQuery.searchAfter(new Object[]{callNumberBrowseItemFirst.getShelfKey()}) + .from(0).size(5); + var precedingResponse = searchRepository.search(request, precedingQuery); + if (precedingResponse.getHits() != null + && precedingResponse.getHits().getTotalHits() != null + && precedingResponse.getHits().getTotalHits().value > 0) { + prev = callNumberBrowseItemFirst.getShelfKey(); + } + String next = null; + var callNumberBrowseItemLast = browseItems.get(browseItems.size() - 1); + succeedingQuery.searchAfter(new Object[]{callNumberBrowseItemLast.getShelfKey()}) + .from(0).size(5); + var succeedingResponse = searchRepository.search(request, succeedingQuery); + if (succeedingResponse.getHits() != null + && succeedingResponse.getHits().getTotalHits() != null + && succeedingResponse.getHits().getTotalHits().value > 0) { + next = callNumberBrowseItemLast.getShelfKey(); + } + + return new BrowseResult() + .totalRecords(precedingResult.getTotalRecords() + succeedingResult.getTotalRecords()) + .prev(prev) + .next(next) + .records(browseItems); + + } + + @Override + protected String getValueForBrowsing(LegacyCallNumberBrowseItem browseItem) { + return browseItem.getShelfKey(); + } + + private BrowseResult additionalPrecedingRequests(BrowseRequest request, + BrowseContext context, + SearchSourceBuilder precedingQuery) { + BrowseResult precedingResult = BrowseResult.empty(); + precedingQuery.size(ADDITIONAL_REQUEST_SIZE); + + while (precedingResult.getRecords().isEmpty()) { + int offset = precedingQuery.from() + precedingQuery.size(); + int size = precedingQuery.size() * 2; + if (offset + size >= 10000) { + break; + } + log.debug("additionalPrecedingRequests:: request offset {}, size {}", offset, size); + precedingQuery.from(offset).size(size); + + var searchResponse = searchRepository.search(request, precedingQuery); + var totalHits = searchResponse.getHits().getTotalHits(); + if (totalHits == null || totalHits.value == 0) { + log.debug("additionalPrecedingRequests:: response have no records"); + break; + } + precedingResult = callNumberBrowseResultConverter.convert(searchResponse, context, request, false); + } + return precedingResult; + } + + private static void highlightMatchingCallNumber(BrowseContext ctx, + String callNumber, + BrowseResult result) { + var items = result.getRecords(); + var anchor = ctx.getAnchor(); + + if (isEmpty(items)) { + result.setRecords(singletonList(getEmptyCallNumberBrowseItem(callNumber, anchor))); + return; + } + + var firstBrowseItem = items.get(0); + if (!StringUtils.equals(firstBrowseItem.getShelfKey(), anchor)) { + var browseItemsWithEmptyValue = new ArrayList(); + browseItemsWithEmptyValue.add(getEmptyCallNumberBrowseItem(callNumber, anchor)); + browseItemsWithEmptyValue.addAll(items); + result.setRecords(browseItemsWithEmptyValue); + return; + } + + firstBrowseItem.setIsAnchor(true); + } + + private static LegacyCallNumberBrowseItem getEmptyCallNumberBrowseItem(String callNumber, String shelfKey) { + return new LegacyCallNumberBrowseItem() + .fullCallNumber(callNumber) + .shelfKey(shelfKey) + .totalRecords(0) + .isAnchor(true); + } +} diff --git a/src/main/java/org/folio/search/service/consortium/ConsortiumSearchHelper.java b/src/main/java/org/folio/search/service/consortium/ConsortiumSearchHelper.java index d21d2be80..54ca6b0af 100644 --- a/src/main/java/org/folio/search/service/consortium/ConsortiumSearchHelper.java +++ b/src/main/java/org/folio/search/service/consortium/ConsortiumSearchHelper.java @@ -241,6 +241,7 @@ private boolean sharedFilterValue(TermQueryBuilder sharedQuery) { private String getFieldForResource(String fieldName, ResourceType resourceName) { if (resourceName.equals(ResourceType.INSTANCE_CONTRIBUTOR) || resourceName.equals(ResourceType.INSTANCE_SUBJECT) + || resourceName.equals(ResourceType.INSTANCE_CALL_NUMBER) || resourceName.equals(ResourceType.INSTANCE_CLASSIFICATION)) { return "instances." + fieldName; } diff --git a/src/main/java/org/folio/search/service/converter/preprocessor/extractor/impl/CallNumberResourceExtractor.java b/src/main/java/org/folio/search/service/converter/preprocessor/extractor/impl/CallNumberResourceExtractor.java index 5e166e756..f1d722de1 100644 --- a/src/main/java/org/folio/search/service/converter/preprocessor/extractor/impl/CallNumberResourceExtractor.java +++ b/src/main/java/org/folio/search/service/converter/preprocessor/extractor/impl/CallNumberResourceExtractor.java @@ -25,6 +25,7 @@ import org.folio.search.model.index.CallNumberResource; import org.folio.search.model.types.ResourceType; import org.folio.search.service.FeatureConfigService; +import org.folio.search.service.consortium.ConsortiumTenantProvider; import org.folio.search.service.converter.preprocessor.extractor.ChildResourceExtractor; import org.folio.search.service.reindex.jdbc.CallNumberRepository; import org.folio.search.utils.CollectionUtils; @@ -48,13 +49,17 @@ public class CallNumberResourceExtractor extends ChildResourceExtractor { private final CallNumberRepository repository; private final JsonConverter jsonConverter; private final FeatureConfigService featureConfigService; + private final ConsortiumTenantProvider tenantProvider; - public CallNumberResourceExtractor(CallNumberRepository repository, JsonConverter jsonConverter, - FeatureConfigService featureConfigService) { + public CallNumberResourceExtractor(CallNumberRepository repository, + JsonConverter jsonConverter, + FeatureConfigService featureConfigService, + ConsortiumTenantProvider tenantProvider) { super(repository); this.repository = repository; this.jsonConverter = jsonConverter; this.featureConfigService = featureConfigService; + this.tenantProvider = tenantProvider; } @Override @@ -189,6 +194,9 @@ private ResourceEvent toResourceEvent(InstanceCallNumberEntityAgg source, String var resource = new CallNumberResource(id, source.fullCallNumber(), source.callNumber(), source.callNumberPrefix(), source.callNumberSuffix(), source.callNumberTypeId(), source.volume(), source.enumeration(), source.chronology(), source.copyNumber(), source.instances()); + for (var instance : source.instances()) { + instance.setShared(tenantProvider.isCentralTenant(instance.getTenantId())); + } return new ResourceEvent() .id(id) .tenant(tenant) diff --git a/src/main/java/org/folio/search/utils/CallNumberUtils.java b/src/main/java/org/folio/search/utils/CallNumberUtils.java index 2959b9024..b1368c19c 100644 --- a/src/main/java/org/folio/search/utils/CallNumberUtils.java +++ b/src/main/java/org/folio/search/utils/CallNumberUtils.java @@ -17,9 +17,9 @@ import lombok.experimental.UtilityClass; import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; -import org.folio.search.domain.dto.CallNumberBrowseItem; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.Item; +import org.folio.search.domain.dto.LegacyCallNumberBrowseItem; import org.folio.search.model.service.BrowseContext; import org.folio.search.model.types.CallNumberType; import org.folio.search.service.consortium.ConsortiumSearchHelper; @@ -213,10 +213,10 @@ public static Long getCallNumberAsLong(String callNumber, int firstPosition) { * @param browseItems - list of CallNumberBrowseItem objects * @return filtered records */ - public static List excludeIrrelevantResultItems(BrowseContext context, + public static List excludeIrrelevantResultItems(BrowseContext context, String callNumberTypeValue, Set folioCallNumberTypes, - List browseItems) { + List browseItems) { var callNumberType = Optional.ofNullable(StringUtils.trimToNull(callNumberTypeValue)); var tenantFilter = ConsortiumSearchHelper.getBrowseFilter(context, BROWSE_TENANT_FILTER_KEY); var locationFilter = ConsortiumSearchHelper.getBrowseFilterValues(context, BROWSE_LOCATION_FILTER_KEY); @@ -247,7 +247,7 @@ private static Optional getValidShelfKey(CallNumber value) { List locationFilter, Optional callNumberType, Set folioCallNumberTypes, - CallNumberBrowseItem item) { + LegacyCallNumberBrowseItem item) { return item.getInstance().getItems() .stream() .filter(i -> tenantIdMatch(tenantFilter, i) && callNumberTypeMatch(callNumberType, folioCallNumberTypes, i) @@ -255,7 +255,7 @@ && locationMatch(locationFilter, i)) .toList(); } - private static boolean isItemRelevant(CallNumberBrowseItem r) { + private static boolean isItemRelevant(LegacyCallNumberBrowseItem r) { Instance instance = r.getInstance(); if (instance != null) { return instance.getItems() diff --git a/src/main/java/org/folio/search/utils/SearchUtils.java b/src/main/java/org/folio/search/utils/SearchUtils.java index 73699aa63..0c3190d6c 100644 --- a/src/main/java/org/folio/search/utils/SearchUtils.java +++ b/src/main/java/org/folio/search/utils/SearchUtils.java @@ -38,7 +38,9 @@ public class SearchUtils { public static final String INSTANCE_HOLDING_FIELD_NAME = "holdings"; public static final String SHARED_FIELD_NAME = "shared"; public static final String TENANT_ID_FIELD_NAME = "tenantId"; - public static final String CALL_NUMBER_BROWSING_FIELD = "callNumber"; + public static final String LEGACY_CALL_NUMBER_BROWSING_FIELD = "callNumber"; + public static final String CALL_NUMBER_BROWSING_FIELD = "fullCallNumber"; + public static final String CALL_NUMBER_TYPE_ID_FIELD = "callNumberTypeId"; public static final String CLASSIFICATION_NUMBER_BROWSING_FIELD = "number"; public static final String CLASSIFICATION_TYPE_ID_FIELD = "typeId"; public static final String TYPED_CALL_NUMBER_BROWSING_FIELD = "typedCallNumber"; @@ -46,6 +48,8 @@ public class SearchUtils { public static final String DEFAULT_SHELVING_ORDER_BROWSING_FIELD = "defaultShelvingOrder"; public static final String LC_SHELVING_ORDER_BROWSING_FIELD = "lcShelvingOrder"; public static final String DEWEY_SHELVING_ORDER_BROWSING_FIELD = "deweyShelvingOrder"; + public static final String NLM_SHELVING_ORDER_BROWSING_FIELD = "nlmShelvingOrder"; + public static final String SUDOC_SHELVING_ORDER_BROWSING_FIELD = "sudocShelvingOrder"; public static final String SUBJECT_BROWSING_FIELD = "value"; public static final String CONTRIBUTOR_BROWSING_FIELD = "name"; public static final String AUTHORITY_BROWSING_FIELD = "headingRef"; @@ -77,7 +81,9 @@ public class SearchUtils { public static final Map BROWSE_FIELDS_MAP = Map.of( ShelvingOrderAlgorithmType.DEFAULT, DEFAULT_SHELVING_ORDER_BROWSING_FIELD, ShelvingOrderAlgorithmType.LC, LC_SHELVING_ORDER_BROWSING_FIELD, - ShelvingOrderAlgorithmType.DEWEY, DEWEY_SHELVING_ORDER_BROWSING_FIELD + ShelvingOrderAlgorithmType.DEWEY, DEWEY_SHELVING_ORDER_BROWSING_FIELD, + ShelvingOrderAlgorithmType.NLM, NLM_SHELVING_ORDER_BROWSING_FIELD, + ShelvingOrderAlgorithmType.SUDOC, SUDOC_SHELVING_ORDER_BROWSING_FIELD ); private static final Pattern NON_ALPHA_NUMERIC_CHARS_PATTERN = Pattern.compile("[^a-zA-Z0-9]"); diff --git a/src/main/resources/model/instance_call_number.json b/src/main/resources/model/instance_call_number.json index 514350730..63acb5142 100644 --- a/src/main/resources/model/instance_call_number.json +++ b/src/main/resources/model/instance_call_number.json @@ -21,6 +21,7 @@ }, "callNumberTypeId": { "index": "keyword", + "searchTypes": [ "facet" ], "showInResponse": [ "browse" ] }, "volume": { @@ -62,11 +63,6 @@ } }, "searchFields": { - "fullCallNumber": { - "type": "search", - "index": "keyword_icu", - "processor": "defaultCallNumberShelvingOrderFieldProcessor" - }, "defaultShelvingOrder": { "type": "search", "index": "keyword_icu", diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index 913d8cdf3..1fd8a722f 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -145,6 +145,9 @@ paths: /browse/call-numbers/instances: $ref: 'paths/browse-call-numbers/browse-call-numbers-instances.yaml' + /browse/call-numbers/{browseOptionId}/instances: + $ref: 'paths/browse-call-numbers/browse-instance-call-numbers.yaml' + /browse/classification-numbers/{browseOptionId}/instances: $ref: 'paths/browse-classification-numbers/browse-instance-classifications.yaml' diff --git a/src/main/resources/swagger.api/parameters/instance-call-number-browse-cql-query.yaml b/src/main/resources/swagger.api/parameters/instance-call-number-browse-cql-query.yaml new file mode 100644 index 000000000..27360a890 --- /dev/null +++ b/src/main/resources/swagger.api/parameters/instance-call-number-browse-cql-query.yaml @@ -0,0 +1,49 @@ +name: query +in: query +required: true +description: | + A CQL query string with filter conditions must include anchor query with range conditions. Anchor field is `number`. + Filters support logic operators `AND` and `OR`. All filters should be combined in parentheses. + Anchor will be included only if `<=` or `>=` are used in the query. Otherwise, the empty row will be added if `highlightMatch` is equal to `true`. + + + + + + + + + + + + + + + + + + + + + + + + +
+ Supported filter options +
OptionData typeSupported operatorsDescription
instances.tenantIdstring==Filter by tenant ID in consortium
instances.sharedboolean==Filter by shared/non-shared in consortium
+schema: + type: string +examples: + browseAround: + value: number >= "DT571.F84" or number < "DT571.F84" + summary: Search for all classification numbers before and after "DT571.F84" + browseForward: + value: number >= "DT571.F84" + summary: Search for all classification numbers after "DT571.F84" + browseBackward: + value: number >= "DT571.F84" + summary: Search for all classification numbers before "DT571.F84" + browseAroundWithFilters: + value: (number >= "DT571.F84" or number < "DT571.F84") and instances.shared==false + summary: Search for local classification numbers before and after "DT571.F84" \ No newline at end of file diff --git a/src/main/resources/swagger.api/paths/browse-call-numbers/browse-call-numbers-instances.yaml b/src/main/resources/swagger.api/paths/browse-call-numbers/browse-call-numbers-instances.yaml index bd70218b1..3375870ef 100644 --- a/src/main/resources/swagger.api/paths/browse-call-numbers/browse-call-numbers-instances.yaml +++ b/src/main/resources/swagger.api/paths/browse-call-numbers/browse-call-numbers-instances.yaml @@ -1,5 +1,5 @@ get: - operationId: browseInstancesByCallNumber + operationId: browseInstancesByCallNumberLegacy summary: Browse Call Numbers description: Provides list of instances for browsing by call number deprecated: true @@ -22,7 +22,7 @@ get: browseResult: $ref: '../../examples/result/browseCallNumberResult.yaml' schema: - $ref: '../../schemas/response/callNumberBrowseResult.yaml' + $ref: '../../schemas/legacy/legacyCallNumberBrowseResult.yaml' '400': $ref: '../../responses/badRequestResponse.yaml' '500': diff --git a/src/main/resources/swagger.api/paths/browse-call-numbers/browse-instance-call-numbers.yaml b/src/main/resources/swagger.api/paths/browse-call-numbers/browse-instance-call-numbers.yaml new file mode 100644 index 000000000..189629dbb --- /dev/null +++ b/src/main/resources/swagger.api/paths/browse-call-numbers/browse-instance-call-numbers.yaml @@ -0,0 +1,27 @@ +get: + operationId: browseInstancesByCallNumber + summary: Browse Instance Call Numbers + description: Provides list of call numbers + tags: + - browse + parameters: + - $ref: '../../parameters/browse-option-id.yaml' + - $ref: '../../parameters/instance-call-number-browse-cql-query.yaml' + - $ref: '../../parameters/browse-limit-param.yaml' + - $ref: '../../parameters/highlight-match.yaml' + - $ref: '../../parameters/preceding-records-count.yaml' + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + responses: + '200': + description: 'Search result for browsing by call number' + content: + application/json: + examples: + browseResult: + $ref: '../../examples/result/browseClassificationNumberResult.yaml' + schema: + $ref: '../../schemas/response/callNumberBrowseResult.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' diff --git a/src/main/resources/swagger.api/paths/browse-classification-numbers/browse-instance-classifications.yaml b/src/main/resources/swagger.api/paths/browse-classification-numbers/browse-instance-classifications.yaml index dcbc7f9cb..f85bbdaca 100644 --- a/src/main/resources/swagger.api/paths/browse-classification-numbers/browse-instance-classifications.yaml +++ b/src/main/resources/swagger.api/paths/browse-classification-numbers/browse-instance-classifications.yaml @@ -1,7 +1,7 @@ get: operationId: browseInstancesByClassificationNumber summary: Browse Instance Classifications - description: Provides list of classifications by classification number + description: Provides list of classification numbers tags: - browse parameters: @@ -13,7 +13,7 @@ get: - $ref: '../../parameters/x-okapi-tenant-header.yaml' responses: '200': - description: 'Search result for browsing by call number' + description: 'Search result for browsing by classification number' content: application/json: examples: diff --git a/src/main/resources/swagger.api/schemas/entity/recordType.yaml b/src/main/resources/swagger.api/schemas/entity/recordType.yaml index 7f82fedc4..33772b0a0 100644 --- a/src/main/resources/swagger.api/schemas/entity/recordType.yaml +++ b/src/main/resources/swagger.api/schemas/entity/recordType.yaml @@ -5,3 +5,4 @@ enum: - contributors - subjects - classifications + - call-numbers diff --git a/src/main/resources/swagger.api/schemas/legacy/legacyCallNumberBrowseItem.yaml b/src/main/resources/swagger.api/schemas/legacy/legacyCallNumberBrowseItem.yaml new file mode 100644 index 000000000..c0ec11a5d --- /dev/null +++ b/src/main/resources/swagger.api/schemas/legacy/legacyCallNumberBrowseItem.yaml @@ -0,0 +1,17 @@ +description: Call-number browse search result item +type: object +properties: + fullCallNumber: + type: string + description: Full call number value to display + shelfKey: + type: string + description: Shelf key value to be used for browsing + isAnchor: + type: boolean + description: Marks if current value is anchor or not + totalRecords: + type: integer + description: Amount of records for the call number value + instance: + $ref: "../../schemas/entity/instance.yaml" diff --git a/src/main/resources/swagger.api/schemas/legacy/legacyCallNumberBrowseResult.yaml b/src/main/resources/swagger.api/schemas/legacy/legacyCallNumberBrowseResult.yaml new file mode 100644 index 000000000..670db171f --- /dev/null +++ b/src/main/resources/swagger.api/schemas/legacy/legacyCallNumberBrowseResult.yaml @@ -0,0 +1,17 @@ +description: Call number browse search response +type: object +properties: + totalRecords: + type: integer + description: Amount of items to display + prev: + type: string + description: Previous value for browsing backward + next: + type: string + description: Next value for browsing forward + items: + type: array + description: List of call number browse items + items: + $ref: "legacyCallNumberBrowseItem.yaml" diff --git a/src/main/resources/swagger.api/schemas/response/callNumberBrowseItem.yaml b/src/main/resources/swagger.api/schemas/response/callNumberBrowseItem.yaml index 2cfbdd2f8..295ee0fc0 100644 --- a/src/main/resources/swagger.api/schemas/response/callNumberBrowseItem.yaml +++ b/src/main/resources/swagger.api/schemas/response/callNumberBrowseItem.yaml @@ -1,18 +1,36 @@ -description: Call-number browse search result item +description: Call number browse search result item type: object properties: fullCallNumber: type: string - description: Full call number value to display - shelfKey: + description: Full call number (includes callNumber, volume, enumeration, chronology, copyNumber, callNumberPrefix) + callNumber: type: string - description: Shelf key value to be used for browsing + description: Call number + callNumberPrefix: + type: string + description: Call number prefix + callNumberSuffix: + type: string + description: Call number suffix + callNumberTypeId: + type: string + description: Call number type ID + volume: + type: string + description: Volume + enumeration: + type: string + description: Enumeration + chronology: + type: string + description: Chronology + copyNumber: + type: string + description: Copy number isAnchor: type: boolean description: Marks if current value is anchor or not totalRecords: type: integer description: Amount of records for the call number value - instance: - description: Related instance object - $ref: "../../schemas/entity/instance.yaml" diff --git a/src/test/java/org/folio/search/controller/BrowseCallNumberAroundIT.java b/src/test/java/org/folio/search/controller/BrowseCallNumberAroundIT.java index 6d3e827a4..cd67dd176 100644 --- a/src/test/java/org/folio/search/controller/BrowseCallNumberAroundIT.java +++ b/src/test/java/org/folio/search/controller/BrowseCallNumberAroundIT.java @@ -16,11 +16,11 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.folio.search.domain.dto.CallNumberBrowseResult; import org.folio.search.domain.dto.Holding; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.Item; import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.search.domain.dto.LegacyCallNumberBrowseResult; import org.folio.search.support.base.BaseIntegrationTest; import org.folio.spring.testing.type.IntegrationTest; import org.junit.jupiter.api.AfterAll; @@ -52,8 +52,8 @@ void browseByCallNumber_browsingAroundPrecedingRecordsWithSame10FirstSymbols() { .param("expandAll", "true") .param("highlightMatch", "true") .param("precedingRecordsCount", "9"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); + assertThat(actual).isEqualTo(new LegacyCallNumberBrowseResult() .totalRecords(64).prev("E 43184 S74 45672").next("E 43184 S75 41243").items(List.of( cnBrowseItemWithNoType(instance("instance #28"), "E 3184 S74 5672"), cnBrowseItemWithNoType(instance("instance #29"), "E 3184 S74 5673"), @@ -82,8 +82,8 @@ void browseByCallNumber_browsingAroundSucceedingRecordsWithSame10FirstSymbols() .param("expandAll", "true") .param("highlightMatch", "true") .param("precedingRecordsCount", "1"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); + assertThat(actual).isEqualTo(new LegacyCallNumberBrowseResult() .totalRecords(64).prev("E 43184 S75 41233").next("G 275 41255").items(List.of( cnBrowseItemWithNoType(instance("instance #03"), "E 3184 S75 1233"), cnBrowseItemWithNoType(instance("instance #04"), "E 3184 S75 1234", true), diff --git a/src/test/java/org/folio/search/controller/BrowseCallNumberConsortiumIT.java b/src/test/java/org/folio/search/controller/BrowseCallNumberConsortiumIT.java index 02f2e3e4d..2bc35524d 100644 --- a/src/test/java/org/folio/search/controller/BrowseCallNumberConsortiumIT.java +++ b/src/test/java/org/folio/search/controller/BrowseCallNumberConsortiumIT.java @@ -1,76 +1,68 @@ package org.folio.search.controller; -import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import static org.awaitility.Durations.TWO_HUNDRED_MILLISECONDS; -import static org.awaitility.Durations.TWO_MINUTES; +import static org.awaitility.Durations.ONE_MINUTE; +import static org.awaitility.Durations.ONE_SECOND; +import static org.folio.search.domain.dto.RecordType.CALL_NUMBERS; +import static org.folio.search.support.base.ApiEndpoints.browseConfigPath; import static org.folio.search.support.base.ApiEndpoints.instanceSearchPath; import static org.folio.search.support.base.ApiEndpoints.recordFacetsPath; -import static org.folio.search.utils.SearchUtils.getIndexName; +import static org.folio.search.utils.CallNumberTestData.CallNumberTypeId.LC; +import static org.folio.search.utils.CallNumberTestData.locations; import static org.folio.search.utils.TestConstants.CENTRAL_TENANT_ID; -import static org.folio.search.utils.TestConstants.FOLIO_CN_TYPE; -import static org.folio.search.utils.TestConstants.LOCAL_CN_TYPE; import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID; import static org.folio.search.utils.TestUtils.array; import static org.folio.search.utils.TestUtils.facet; import static org.folio.search.utils.TestUtils.facetItem; import static org.folio.search.utils.TestUtils.mapOf; +import static org.folio.search.utils.TestUtils.mockCallNumberTypes; import static org.folio.search.utils.TestUtils.parseResponse; -import static org.folio.search.utils.TestUtils.randomId; import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.opensearch.index.query.QueryBuilders.matchAllQuery; -import static org.opensearch.search.builder.SearchSourceBuilder.searchSource; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Stream; -import org.assertj.core.util.Lists; +import org.folio.search.domain.dto.BrowseConfig; +import org.folio.search.domain.dto.BrowseOptionType; +import org.folio.search.domain.dto.BrowseType; import org.folio.search.domain.dto.Facet; import org.folio.search.domain.dto.FacetResult; -import org.folio.search.domain.dto.Holding; -import org.folio.search.domain.dto.Instance; -import org.folio.search.domain.dto.Item; -import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; -import org.folio.search.domain.dto.RecordType; +import org.folio.search.domain.dto.ShelvingOrderAlgorithmType; import org.folio.search.model.types.ResourceType; import org.folio.search.support.base.BaseConsortiumIntegrationTest; +import org.folio.search.utils.CallNumberTestData; import org.folio.spring.testing.type.IntegrationTest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.opensearch.action.search.SearchRequest; -import org.opensearch.client.RequestOptions; -import org.opensearch.client.RestHighLevelClient; -import org.springframework.beans.factory.annotation.Autowired; -@Disabled @IntegrationTest class BrowseCallNumberConsortiumIT extends BaseConsortiumIntegrationTest { - private static final String[] LOCATIONS = array("location 1", "location 2"); - private static final Instance[] INSTANCES_CENTRAL = instancesCentral(); - private static final Instance[] INSTANCES_MEMBER = instancesMember(); + public static final String LOCATION_FACET = "instances.locationId"; + public static final String TENANT_FACET = "instances.tenantId"; @BeforeAll - static void prepare(@Autowired RestHighLevelClient restHighLevelClient) throws InterruptedException { - setUpTenant(CENTRAL_TENANT_ID, INSTANCES_CENTRAL); + static void prepare() { + var allInstances = CallNumberTestData.instances(); + setUpTenant(CENTRAL_TENANT_ID); setUpTenant(MEMBER_TENANT_ID); - saveRecords(MEMBER_TENANT_ID, instanceSearchPath(), asList(INSTANCES_MEMBER), - 4, + var centralInstances = allInstances.subList(0, 30); + saveRecords(CENTRAL_TENANT_ID, instanceSearchPath(), centralInstances, centralInstances.size(), + instance -> inventoryApi.createInstance(CENTRAL_TENANT_ID, instance)); + var memberInstances = allInstances.subList(30, allInstances.size()); + saveRecords(MEMBER_TENANT_ID, instanceSearchPath(), memberInstances, allInstances.size(), instance -> inventoryApi.createInstance(MEMBER_TENANT_ID, instance)); - await().atMost(TWO_MINUTES).pollInterval(TWO_HUNDRED_MILLISECONDS).untilAsserted(() -> { - var searchRequest = new SearchRequest() - .source(searchSource().query(matchAllQuery()).trackTotalHits(true).from(0)) - .indices(getIndexName(ResourceType.INSTANCE, CENTRAL_TENANT_ID)); - var searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); - assertThat(searchResponse.getHits().getTotalHits().value).isEqualTo(4); + await().atMost(ONE_MINUTE).pollInterval(ONE_SECOND).untilAsserted(() -> { + var counted = countIndexDocument(ResourceType.INSTANCE_CALL_NUMBER, CENTRAL_TENANT_ID); + assertThat(counted).isEqualTo(100); }); } @@ -79,12 +71,16 @@ static void cleanUp() { removeTenant(); } + @BeforeEach + void setUp() { + updateLcConfig(List.of(UUID.fromString(LC.getId()))); + } + @MethodSource("facetQueriesProvider") - @ParameterizedTest(name = "[{index}] query={0}, facets={1}") - @DisplayName("getFacetsForInstances_parameterized") - void getFacetsForInstances_positive(String tenantId, String query, String[] facets, Map expected) { - var actual = parseResponse( - doGet(recordFacetsPath(RecordType.INSTANCES, query, facets), tenantId), FacetResult.class); + @ParameterizedTest(name = "[{index}] tenant={0} query={1}, facets={2}") + @DisplayName("getFacetsForCallNumbers_ecs_parameterized") + void getFacetsForCallNumbers_positive(String tenantId, String query, String[] facets, Map expected) { + var actual = parseResponse(doGet(recordFacetsPath(CALL_NUMBERS, query, facets), tenantId), FacetResult.class); expected.forEach((facetName, expectedFacet) -> { var actualFacet = actual.getFacets().get(facetName); @@ -96,73 +92,45 @@ void getFacetsForInstances_positive(String tenantId, String query, String[] face } private static Stream facetQueriesProvider() { + var locations = locations(); return Stream.of( - arguments(CENTRAL_TENANT_ID, "cql.allRecords=1", array("item.effectiveLocationId"), mapOf( - "items.effectiveLocationId", facet(facetItem(LOCATIONS[0], 1), facetItem(LOCATIONS[1], 1)))), - arguments(CENTRAL_TENANT_ID, "cql.allRecords=1", array("holdings.tenantId"), mapOf( - "holdings.tenantId", facet(facetItem(CENTRAL_TENANT_ID, 1), facetItem(MEMBER_TENANT_ID, 1)))), - arguments(CENTRAL_TENANT_ID, "callNumberType=\"local\"", array("item.effectiveLocationId"), mapOf( - "items.effectiveLocationId", facet(facetItem(LOCATIONS[0], 1), facetItem(LOCATIONS[1], 1)))), - arguments(CENTRAL_TENANT_ID, "callNumberType=\"local\"", array("holdings.tenantId"), mapOf( - "holdings.tenantId", facet(facetItem(CENTRAL_TENANT_ID, 1), facetItem(MEMBER_TENANT_ID, 1)))), - arguments(MEMBER_TENANT_ID, "cql.allRecords=1", array("item.effectiveLocationId"), mapOf( - "items.effectiveLocationId", facet(facetItem(LOCATIONS[0], 3), facetItem(LOCATIONS[1], 1)))), - arguments(MEMBER_TENANT_ID, "cql.allRecords=1", array("holdings.tenantId"), mapOf( - "holdings.tenantId", facet(facetItem(CENTRAL_TENANT_ID, 1), facetItem(MEMBER_TENANT_ID, 3)))), - arguments(MEMBER_TENANT_ID, "callNumberType=\"local\"", array("item.effectiveLocationId"), mapOf( - "items.effectiveLocationId", facet(facetItem(LOCATIONS[0], 2), facetItem(LOCATIONS[1], 1)))), - arguments(MEMBER_TENANT_ID, "callNumberType=\"local\"", array("holdings.tenantId"), mapOf( - "holdings.tenantId", facet(facetItem(CENTRAL_TENANT_ID, 1), facetItem(MEMBER_TENANT_ID, 2)))) + arguments(CENTRAL_TENANT_ID, "cql.allRecords=1", array(LOCATION_FACET), + expectedLocationFacet(locations, 27, 20, 13)), + arguments(CENTRAL_TENANT_ID, "cql.allRecords=1", array(TENANT_FACET), mapOf(TENANT_FACET, + facet(facetItem(CENTRAL_TENANT_ID, 60)))), + arguments(CENTRAL_TENANT_ID, "callNumberTypeId=\"" + LC.getId() + "\"", array(LOCATION_FACET), + expectedLocationFacet(locations, 6, 4, 2)), + arguments(MEMBER_TENANT_ID, "cql.allRecords=1", array(LOCATION_FACET), + expectedLocationFacet(locations, 42, 34, 24)), + arguments(MEMBER_TENANT_ID, "cql.allRecords=1", array(TENANT_FACET), mapOf(TENANT_FACET, + facet(facetItem(CENTRAL_TENANT_ID, 60), facetItem(MEMBER_TENANT_ID, 40)))), + arguments(MEMBER_TENANT_ID, "instances.shared=false", array(LOCATION_FACET), + expectedLocationFacet(locations, 15, 14, 11)), + arguments(MEMBER_TENANT_ID, "instances.shared=false", array(TENANT_FACET), mapOf(TENANT_FACET, + facet(facetItem(MEMBER_TENANT_ID, 40)))) ); } - private static Instance[] instancesCentral() { - return Stream.of( - Lists.list("instance #01", CENTRAL_TENANT_ID, null, null, List.of()), - List.of("instance #02", CENTRAL_TENANT_ID, LOCAL_CN_TYPE, LOCATIONS[0], List.of("central"))) - .map(BrowseCallNumberConsortiumIT::instance) - .toArray(Instance[]::new); + private static Map expectedLocationFacet(Map locations, + int totalRecords1, + int totalRecords2, int totalRecords3) { + return mapOf(LOCATION_FACET, facet( + facetItem(locations.get(1), totalRecords1), + facetItem(locations.get(2), totalRecords2), + facetItem(locations.get(3), totalRecords3) + ) + ); } - private static Instance[] instancesMember() { - return Stream.of( - List.of("instance #02", MEMBER_TENANT_ID, LOCAL_CN_TYPE, LOCATIONS[1], List.of("member 1", "member 2")), - List.of("instance #03", MEMBER_TENANT_ID, LOCAL_CN_TYPE, LOCATIONS[0], List.of("member 3")), - List.of("instance #04", MEMBER_TENANT_ID, FOLIO_CN_TYPE, LOCATIONS[0], List.of("member 4"))) - .map(BrowseCallNumberConsortiumIT::instance) - .toArray(Instance[]::new); - } + private static void updateLcConfig(List typeIds) { + var config = new BrowseConfig() + .id(BrowseOptionType.LC) + .shelvingAlgorithm(ShelvingOrderAlgorithmType.LC) + .typeIds(typeIds); - @SuppressWarnings("unchecked") - private static Instance instance(List data) { - var tenantId = (String) data.get(1); - var items = ((List) data.get(4)).stream() - .map(callNumber -> new Item() - .tenantId(tenantId) - .id(randomId()) - .discoverySuppress(false) - .effectiveCallNumberComponents(new ItemEffectiveCallNumberComponents() - .callNumber(callNumber).typeId(String.valueOf(data.get(2)))) - .effectiveLocationId(String.valueOf(data.get(3))) - .effectiveShelvingOrder(callNumber)) - .toList(); - var holdings = ((List) data.get(4)).stream() - .map(callNumber -> new Holding() - .tenantId(tenantId) - .id(randomId()) - ) - .toList(); - - var title = (String) data.get(0); - return new Instance() - .id(title.equals("instance #02") ? "840d391f-ae06-4cbc-b66b-0ad317d193a2" : UUID.randomUUID().toString()) - .title(title) - .staffSuppress(false) - .discoverySuppress(false) - .isBoundWith(false) - .shared(CENTRAL_TENANT_ID.equals(tenantId)) - .tenantId(tenantId) - .items(items) - .holdings(holdings); + var stub = mockCallNumberTypes(okapi.wireMockServer(), typeIds.toArray(new UUID[0])); + doPut(browseConfigPath(BrowseType.CALL_NUMBER, BrowseOptionType.LC), CENTRAL_TENANT_ID, config); + okapi.wireMockServer().removeStub(stub); } + } diff --git a/src/test/java/org/folio/search/controller/BrowseCallNumberIT.java b/src/test/java/org/folio/search/controller/BrowseCallNumberIT.java index 7c98bd7b6..3a07eb4f8 100644 --- a/src/test/java/org/folio/search/controller/BrowseCallNumberIT.java +++ b/src/test/java/org/folio/search/controller/BrowseCallNumberIT.java @@ -2,36 +2,50 @@ import static java.util.Collections.emptyList; import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; -import static org.folio.search.domain.dto.TenantConfiguredFeature.BROWSE_CN_INTERMEDIATE_VALUES; +import static org.awaitility.Awaitility.await; +import static org.awaitility.Durations.ONE_MINUTE; +import static org.awaitility.Durations.ONE_SECOND; +import static org.folio.search.support.base.ApiEndpoints.browseConfigPath; import static org.folio.search.support.base.ApiEndpoints.instanceCallNumberBrowsePath; +import static org.folio.search.support.base.ApiEndpoints.recordFacetsPath; +import static org.folio.search.utils.CallNumberTestData.CallNumberTypeId.LC; +import static org.folio.search.utils.CallNumberTestData.callNumbers; +import static org.folio.search.utils.CallNumberTestData.locations; import static org.folio.search.utils.TestConstants.TENANT_ID; -import static org.folio.search.utils.TestUtils.cleanupActual; -import static org.folio.search.utils.TestUtils.cnBrowseItem; -import static org.folio.search.utils.TestUtils.getShelfKeyFromCallNumber; +import static org.folio.search.utils.TestUtils.array; +import static org.folio.search.utils.TestUtils.facet; +import static org.folio.search.utils.TestUtils.facetItem; +import static org.folio.search.utils.TestUtils.mapOf; +import static org.folio.search.utils.TestUtils.mockCallNumberTypes; import static org.folio.search.utils.TestUtils.parseResponse; -import static org.folio.search.utils.TestUtils.randomId; -import static org.hamcrest.Matchers.is; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; import java.util.stream.Stream; +import org.folio.search.domain.dto.BrowseConfig; +import org.folio.search.domain.dto.BrowseOptionType; +import org.folio.search.domain.dto.BrowseType; +import org.folio.search.domain.dto.CallNumberBrowseItem; import org.folio.search.domain.dto.CallNumberBrowseResult; -import org.folio.search.domain.dto.Holding; +import org.folio.search.domain.dto.Facet; +import org.folio.search.domain.dto.FacetResult; import org.folio.search.domain.dto.Instance; -import org.folio.search.domain.dto.Item; -import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.search.domain.dto.RecordType; +import org.folio.search.domain.dto.ShelvingOrderAlgorithmType; +import org.folio.search.model.index.CallNumberResource; +import org.folio.search.model.types.ResourceType; import org.folio.search.support.base.BaseIntegrationTest; +import org.folio.search.utils.CallNumberTestData; import org.folio.spring.testing.type.IntegrationTest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -39,13 +53,13 @@ @IntegrationTest class BrowseCallNumberIT extends BaseIntegrationTest { - private static final Instance[] INSTANCES = instances(); - private static final Map INSTANCE_MAP = - Arrays.stream(INSTANCES).collect(toMap(Instance::getTitle, identity())); - @BeforeAll static void prepare() { - setUpTenant(List.of(jsonPath("sum($.instances..items.length())", is(57.0))), INSTANCES); + setUpTenant(CallNumberTestData.instances().toArray(Instance[]::new)); + await().atMost(ONE_MINUTE).pollInterval(ONE_SECOND).untilAsserted(() -> { + var counted = countIndexDocument(ResourceType.INSTANCE_CALL_NUMBER, TENANT_ID); + assertThat(counted).isEqualTo(100); + }); } @AfterAll @@ -53,426 +67,188 @@ static void cleanUp() { removeTenant(); } + @BeforeEach + void setUp() { + updateLcConfig(List.of(UUID.fromString(LC.getId()))); + } + @MethodSource("callNumberBrowsingDataProvider") @DisplayName("browseByCallNumber_parameterized") - @ParameterizedTest(name = "[{index}] query={0}, value=''{1}'', limit={2}") - void browseByCallNumber_parameterized(String query, String anchor, Integer limit, CallNumberBrowseResult expected) { - var request = get(instanceCallNumberBrowsePath()) + @ParameterizedTest(name = "[{index}] query={0}, option={1}, value=''{2}'', limit={3}") + void browseByCallNumber_parameterized(String query, BrowseOptionType optionType, String input, Integer limit, + CallNumberBrowseResult expected) { + var request = get(instanceCallNumberBrowsePath(optionType)) .param("expandAll", "true") - .param("query", prepareQuery(query, '"' + anchor + '"')) + .param("query", prepareQuery(query, '"' + input + '"')) .param("limit", String.valueOf(limit)); var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); - cleanupActual(actual); - actual.setTotalRecords(null); assertThat(actual).isEqualTo(expected); } - @Test - void browseByCallNumber_browsingAroundWhenPrecedingRecordsCountIsSpecified() { - var request = get(instanceCallNumberBrowsePath()) - .param("query", prepareQuery("callNumber < {value} or callNumber >= {value}", "\"CE 16 B6713 X 41993\"")) - .param("limit", "5") - .param("expandAll", "true") - .param("precedingRecordsCount", "4"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); - cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() - .totalRecords(37).prev("AB 214 C72 NO 3220").next("CE 216 B6713 X 541993").items(List.of( - cnBrowseItem(instance("instance #31"), "AB 14 C72 NO 220"), - cnBrowseItem(instance("instance #25"), "AC 11 A4 VOL 235"), - cnBrowseItem(instance("instance #08"), "AC 11 A67 X 42000"), - cnBrowseItem(instance("instance #18"), "AC 11 E8 NO 14 P S1487"), - cnBrowseItem(instance("instance #44"), "CE 16 B6713 X 41993", true) - ))); - } - - //https://issues.folio.org/browse/MSEARCH-513 - @Test - void browseByCallNumber_browsingVeryFirstCallNumberWithNoException() { - var request = get(instanceCallNumberBrowsePath()) - .param("query", prepareQuery("callNumber >= {value} or callNumber < {value}", "\"AB 14 C72 NO 220\"")) - .param("limit", "10") - .param("highlightMatch", "true") - .param("expandAll", "true") - .param("precedingRecordsCount", "5"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); - cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() - .totalRecords(32).prev("AB 214 C72 NO 3220").next("CE 216 B6713 X 541993").items(List.of( - cnBrowseItem(instance("instance #31"), "AB 14 C72 NO 220", true), - cnBrowseItem(instance("instance #25"), "AC 11 A4 VOL 235"), - cnBrowseItem(instance("instance #08"), "AC 11 A67 X 42000"), - cnBrowseItem(instance("instance #18"), "AC 11 E8 NO 14 P S1487"), - cnBrowseItem(instance("instance #44"), "CE 16 B6713 X 41993") - ))); - } + @MethodSource("facetQueriesProvider") + @ParameterizedTest(name = "[{index}] query={0}, facets={1}") + @DisplayName("getFacetsForCallNumbers_parameterized") + void getFacetsForSubjects_parameterized(String query, String[] facets, Map expected) { + var actual = parseResponse(doGet(recordFacetsPath(RecordType.CALL_NUMBERS, query, facets)), FacetResult.class); - @Test - void browseByCallNumber_browsingAroundWhenMultipleAnchors() { - var request = get(instanceCallNumberBrowsePath()) - .param("query", prepareQuery("callNumber < {value} or callNumber >= {value}", "\"J29.29:M54/990\"")) - .param("limit", "5") - .param("expandAll", "true") - .param("precedingRecordsCount", "4"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); - cleanupActual(actual); - var expected = new CallNumberBrowseResult() - .totalRecords(41).prev("GA 216 D64 541548A").next("J 229.29 M54 3990").items(List.of( - cnBrowseItem(instance("instance #39"), "GA 16 D64 41548A"), - cnBrowseItem(instance("instance #30"), "GA 16 G32 41557 V1"), - cnBrowseItem(instance("instance #30"), "GA 16 G32 41557 V2"), - cnBrowseItem(instance("instance #30"), "GA 16 G32 41557 V3"), - cnBrowseItem(instance("instance #47"), "J29.29:M54/990", true) - )); - assertThat(actual).isEqualTo(expected); - } + expected.forEach((facetName, expectedFacet) -> { + var actualFacet = actual.getFacets().get(facetName); - @Test - void browseByCallNumber_browsingAroundWithoutHighlightMatch() { - var request = get(instanceCallNumberBrowsePath()) - .param("query", prepareQuery("callNumber < {value} or callNumber >= {value}", "\"CE 16 B6713 X 41993\"")) - .param("limit", "5") - .param("expandAll", "true") - .param("highlightMatch", "false"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); - cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() - .totalRecords(37).prev("AC 211 A67 X 542000").next("CE 216 D86 X 541998").items(List.of( - cnBrowseItem(instance("instance #08"), "AC 11 A67 X 42000"), - cnBrowseItem(instance("instance #18"), "AC 11 E8 NO 14 P S1487"), - cnBrowseItem(instance("instance #44"), "CE 16 B6713 X 41993"), - cnBrowseItem(instance("instance #45"), "CE 16 B6724 41993"), - cnBrowseItem(instance("instance #04"), "CE 16 D86 X 41998") - ))); + assertThat(actualFacet).isNotNull(); + assertThat(actualFacet.getValues()) + .containsExactlyInAnyOrderElementsOf(expectedFacet.getValues()); + }); } - @Test - void browseByCalNumber_browseAroundWithEnabledIntermediateValues() throws InterruptedException { - enableFeature(BROWSE_CN_INTERMEDIATE_VALUES); - - var request = get(instanceCallNumberBrowsePath()) - .param("query", prepareQuery("callNumber < {value} or callNumber >= {value}", "\"DA 3880 O5 C3 V1\"")) - .param("limit", "7") - .param("expandAll", "true"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); - cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() - .totalRecords(58).prev("DA 43870 B55 541868").next("DA 43880 O6 D5").items(List.of( - cnBrowseItem(instance("instance #41"), "DA 3870 B55 41868"), - cnBrowseItem(instance("instance #07"), "DA 3870 H47 41975"), - cnBrowseItem(instance("instance #11"), "DA 3880 K56 M27 41984"), - cnBrowseItem(instance("instance #32"), "DA 3880 O5 C3 V1", true), - cnBrowseItem(instance("instance #32"), "DA 3880 O5 C3 V2"), - cnBrowseItem(instance("instance #32"), "DA 3880 O5 C3 V3"), - cnBrowseItem(instance("instance #29"), "DA 3880 O6 D5") - ))); - - disableFeature(BROWSE_CN_INTERMEDIATE_VALUES); + private static Stream facetQueriesProvider() { + var locations = locations(); + return Stream.of( + arguments("cql.allRecords=1", array("instances.locationId"), mapOf("instances.locationId", + facet(facetItem(locations.get(1), 42), facetItem(locations.get(2), 34), facetItem(locations.get(3), 24)))), + arguments("callNumberTypeId=\"" + LC.getId() + "\"", array("instances.locationId"), mapOf("instances.locationId", + facet(facetItem(locations.get(1), 8), facetItem(locations.get(2), 8), facetItem(locations.get(3), 4)))) + ); } private static Stream callNumberBrowsingDataProvider() { - var aroundQuery = "callNumber > {value} or callNumber < {value}"; - var aroundIncludingQuery = "callNumber >= {value} or callNumber < {value}"; - var forwardQuery = "itemEffectiveShelvingOrder > {value}"; - var forwardIncludingQuery = "itemEffectiveShelvingOrder >= {value}"; - var backwardQuery = "itemEffectiveShelvingOrder < {value}"; - var backwardIncludingQuery = "itemEffectiveShelvingOrder <= {value}"; + var aroundQuery = "fullCallNumber >= {value} or fullCallNumber < {value}"; + var forwardQuery = "fullCallNumber > {value}"; + var backwardQuery = "fullCallNumber < {value}"; - var firstAnchorCallNumber = "CE 210 K297 41858"; - var secondAnchorCallNumber = "DA 3890 A1"; - var firstAnchorShelfKey = getShelfKeyFromCallNumber(firstAnchorCallNumber); - var secondAnchorShelfKey = getShelfKeyFromCallNumber(secondAnchorCallNumber); + var callNumbers = callNumbers().stream() + .map(CallNumberTestData.CallNumberTestDataRecord::callNumber) + .collect(Collectors.toMap(callNumberResource -> Integer.parseInt(callNumberResource.id()), identity())); return Stream.of( - arguments(aroundQuery, firstAnchorCallNumber, 5, new CallNumberBrowseResult() - .prev("CE 216 B6724 541993").next("DA 43700 C95 NO 218").items(List.of( - cnBrowseItem(instance("instance #45"), "CE 16 B6724 41993"), - cnBrowseItem(instance("instance #04"), "CE 16 D86 X 41998"), - cnBrowseItem(0, "CE 210 K297 41858", true), - cnBrowseItem(instance("instance #36"), "DA 3700 B91 L79"), - cnBrowseItem(instance("instance #09"), "DA 3700 C95 NO 18") - ))), - - arguments(aroundQuery, secondAnchorCallNumber, 5, new CallNumberBrowseResult() - .prev("DA 43880 O6 M81").next("DA 43890 A2 B76 542002").items(List.of( - cnBrowseItem(instance("instance #13"), "DA 3880 O6 M81"), - cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96"), - cnBrowseItem(0, "DA 3890 A1", true), - cnBrowseItem(instance("instance #14"), "DA 3890 A1 I72 41885"), - cnBrowseItem(instance("instance #22"), "DA 3890 A2 B76 42002") - ))), - - arguments(aroundIncludingQuery, firstAnchorCallNumber, 5, new CallNumberBrowseResult() - .prev("CE 216 B6724 541993").next("DA 43700 C95 NO 218").items(List.of( - cnBrowseItem(instance("instance #45"), "CE 16 B6724 41993"), - cnBrowseItem(instance("instance #04"), "CE 16 D86 X 41998"), - cnBrowseItem(instance("instance #38"), "CE 210 K297 41858", true), - cnBrowseItem(instance("instance #36"), "DA 3700 B91 L79"), - cnBrowseItem(instance("instance #09"), "DA 3700 C95 NO 18") - ))), - - arguments(aroundIncludingQuery, secondAnchorCallNumber, 5, new CallNumberBrowseResult() - .prev("DA 43880 O6 M81").next("DA 43890 A2 B76 542002").items(List.of( - cnBrowseItem(instance("instance #13"), "DA 3880 O6 M81"), - cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96"), - cnBrowseItem(0, "DA 3890 A1", true), - cnBrowseItem(instance("instance #14"), "DA 3890 A1 I72 41885"), - cnBrowseItem(instance("instance #22"), "DA 3890 A2 B76 42002") - ))), - - // checks order of closely placed call-numbers - arguments(aroundIncludingQuery, secondAnchorCallNumber, 30, new CallNumberBrowseResult() - .prev("DA 43870 H47 541975").next("E 3211 N52 VOL 214").items(List.of( - cnBrowseItem(instance("instance #07"), "DA 3870 H47 41975"), - cnBrowseItem(instance("instance #11"), "DA 3880 K56 M27 41984"), - cnBrowseItem(instance("instance #32"), "DA 3880 O5 C3 V1"), - cnBrowseItem(instance("instance #32"), "DA 3880 O5 C3 V2"), - cnBrowseItem(instance("instance #32"), "DA 3880 O5 C3 V3"), - cnBrowseItem(instance("instance #29"), "DA 3880 O6 D5"), - cnBrowseItem(instance("instance #01"), "DA 3880 O6 J72"), - cnBrowseItem(instance("instance #03"), "DA 3880 O6 L5 41955"), - cnBrowseItem(instance("instance #06"), "DA 3880 O6 L6 V1"), - cnBrowseItem(instance("instance #06"), "DA 3880 O6 L6 V2"), - cnBrowseItem(instance("instance #20"), "DA 3880 O6 L75"), - cnBrowseItem(instance("instance #15"), "DA 3880 O6 L76"), - cnBrowseItem(instance("instance #05"), "DA 3880 O6 M15"), - cnBrowseItem(instance("instance #13"), "DA 3880 O6 M81"), - cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96"), - cnBrowseItem(0, "DA 3890 A1", true), - cnBrowseItem(instance("instance #14"), "DA 3890 A1 I72 41885"), - cnBrowseItem(instance("instance #22"), "DA 3890 A2 B76 42002"), - cnBrowseItem(instance("instance #19"), "DA 3890 A2 F57 42011"), - cnBrowseItem(instance("instance #24"), "DA 3900 C39 NO 11"), - cnBrowseItem(instance("instance #34"), "DA 3900 C89 V1"), - cnBrowseItem(instance("instance #34"), "DA 3900 C89 V2"), - cnBrowseItem(instance("instance #34"), "DA 3900 C89 V3"), - cnBrowseItem(instance("instance #28"), "DB 11 A31 BD 3124"), - cnBrowseItem(instance("instance #23"), "DB 11 A66 SUPPL NO 11"), - cnBrowseItem(instance("instance #10"), "DC 3201 B34 41972"), - cnBrowseItem(instance("instance #35"), "E 12.11 I12 288 D"), - cnBrowseItem(instance("instance #33"), "E 12.11 I2 298"), - cnBrowseItem(instance("instance #27"), "E 211 A506"), - cnBrowseItem(instance("instance #12"), "E 211 N52 VOL 14") - ))), - - // checks if collapsing by the same result works correctly - arguments(aroundIncludingQuery, "FC", 5, new CallNumberBrowseResult() - .prev("FA 542010 43546 3256").next("G 545831 S2").items(List.of( - cnBrowseItem(instance("instance #43"), "FA 42010 3546 256"), - cnBrowseItem(instance("instance #42"), "FA 46252 3977 12 237"), - cnBrowseItem(0, "FC", true), - cnBrowseItem(2, "FC 17 B89"), - cnBrowseItem(instance("instance #31"), "G 45831 S2") - ))), - - // checks if collapsing by the same result works correctly - arguments(aroundIncludingQuery, "fc", 5, new CallNumberBrowseResult() - .prev("FA 542010 43546 3256").next("G 545831 S2").items(List.of( - cnBrowseItem(instance("instance #43"), "FA 42010 3546 256"), - cnBrowseItem(instance("instance #42"), "FA 46252 3977 12 237"), - cnBrowseItem(0, "fc", true), - cnBrowseItem(2, "FC 17 B89"), - cnBrowseItem(instance("instance #31"), "G 45831 S2") - ))), - - // browsing forward - arguments(forwardQuery, firstAnchorShelfKey, 5, new CallNumberBrowseResult() - .prev("DA 43700 B91 L79").next("DA 43880 K56 M27 541984").items(List.of( - cnBrowseItem(instance("instance #36"), "DA 3700 B91 L79"), - cnBrowseItem(instance("instance #09"), "DA 3700 C95 NO 18"), - cnBrowseItem(instance("instance #41"), "DA 3870 B55 41868"), - cnBrowseItem(instance("instance #07"), "DA 3870 H47 41975"), - cnBrowseItem(instance("instance #11"), "DA 3880 K56 M27 41984") - ))), - - arguments(forwardQuery, secondAnchorShelfKey, 5, new CallNumberBrowseResult() - .prev("DA 43890 A1 I72 541885").next("DA 43900 C89 V1").items(List.of( - cnBrowseItem(instance("instance #14"), "DA 3890 A1 I72 41885"), - cnBrowseItem(instance("instance #22"), "DA 3890 A2 B76 42002"), - cnBrowseItem(instance("instance #19"), "DA 3890 A2 F57 42011"), - cnBrowseItem(instance("instance #24"), "DA 3900 C39 NO 11"), - cnBrowseItem(instance("instance #34"), "DA 3900 C89 V1") - ))), - - // checks if collapsing works in forward direction - arguments(forwardQuery, "F", 5, new CallNumberBrowseResult() - .prev("F PR1866.S63 V.1 C.1").next("FC 217 B89").items(List.of( - cnBrowseItem(instance("instance #46"), "F PR1866.S63 V.1 C.1"), - cnBrowseItem(instance("instance #27"), "F 43733 L370 41992"), - cnBrowseItem(instance("instance #43"), "FA 42010 3546 256"), - cnBrowseItem(instance("instance #42"), "FA 46252 3977 12 237"), - cnBrowseItem(2, "FC 17 B89") - ))), - - arguments(forwardQuery, "Z", 10, new CallNumberBrowseResult() - .prev(null).next(null).items(emptyList())), - - arguments(forwardIncludingQuery, firstAnchorShelfKey, 5, new CallNumberBrowseResult() - .prev("CE 3210 K297 541858").next("DA 43870 H47 541975").items(List.of( - cnBrowseItem(instance("instance #38"), "CE 210 K297 41858"), - cnBrowseItem(instance("instance #36"), "DA 3700 B91 L79"), - cnBrowseItem(instance("instance #09"), "DA 3700 C95 NO 18"), - cnBrowseItem(instance("instance #41"), "DA 3870 B55 41868"), - cnBrowseItem(instance("instance #07"), "DA 3870 H47 41975") - ))), - - arguments(forwardIncludingQuery, secondAnchorShelfKey, 5, new CallNumberBrowseResult() - .prev("DA 43890 A1 I72 541885").next("DA 43900 C89 V1").items(List.of( - cnBrowseItem(instance("instance #14"), "DA 3890 A1 I72 41885"), - cnBrowseItem(instance("instance #22"), "DA 3890 A2 B76 42002"), - cnBrowseItem(instance("instance #19"), "DA 3890 A2 F57 42011"), - cnBrowseItem(instance("instance #24"), "DA 3900 C39 NO 11"), - cnBrowseItem(instance("instance #34"), "DA 3900 C89 V1") - ))), - - // browsing backward - arguments(backwardQuery, firstAnchorShelfKey, 5, new CallNumberBrowseResult() - .prev("AC 211 A67 X 542000").next("CE 216 D86 X 541998").items(List.of( - cnBrowseItem(instance("instance #08"), "AC 11 A67 X 42000"), - cnBrowseItem(instance("instance #18"), "AC 11 E8 NO 14 P S1487"), - cnBrowseItem(instance("instance #44"), "CE 16 B6713 X 41993"), - cnBrowseItem(instance("instance #45"), "CE 16 B6724 41993"), - cnBrowseItem(instance("instance #04"), "CE 16 D86 X 41998") - ))), - - arguments(backwardQuery, secondAnchorShelfKey, 5, new CallNumberBrowseResult() - .prev("DA 43880 O6 L75").next("DA 43880 O6 M96").items(List.of( - cnBrowseItem(instance("instance #20"), "DA 3880 O6 L75"), - cnBrowseItem(instance("instance #15"), "DA 3880 O6 L76"), - cnBrowseItem(instance("instance #05"), "DA 3880 O6 M15"), - cnBrowseItem(instance("instance #13"), "DA 3880 O6 M81"), - cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96") - ))), - - // check that collapsing works for browsing backward - arguments(backwardQuery, "G", 5, new CallNumberBrowseResult() - .prev("F PR1866.S63 V.1 C.1").next("FC 217 B89").items(List.of( - cnBrowseItem(instance("instance #46"), "F PR1866.S63 V.1 C.1"), - cnBrowseItem(instance("instance #27"), "F 43733 L370 41992"), - cnBrowseItem(instance("instance #43"), "FA 42010 3546 256"), - cnBrowseItem(instance("instance #42"), "FA 46252 3977 12 237"), - cnBrowseItem(2, "FC 17 B89") - ))), - - arguments(backwardQuery, "F 11", 5, new CallNumberBrowseResult() - .prev("E 212.11 I12 3288 D").next("F PR1866.S63 V.1 C.1").items(List.of( - cnBrowseItem(instance("instance #35"), "E 12.11 I12 288 D"), - cnBrowseItem(instance("instance #33"), "E 12.11 I2 298"), - cnBrowseItem(instance("instance #27"), "E 211 A506"), - cnBrowseItem(instance("instance #12"), "E 211 N52 VOL 14"), - cnBrowseItem(instance("instance #46"), "F PR1866.S63 V.1 C.1") - ))), - - arguments(backwardQuery, "A", 10, new CallNumberBrowseResult() - .prev(null).next(null).items(emptyList())), - - arguments(backwardIncludingQuery, firstAnchorShelfKey, 5, new CallNumberBrowseResult() - .prev("AC 211 E8 NO 214 P S1487").next("CE 3210 K297 541858").items(List.of( - cnBrowseItem(instance("instance #18"), "AC 11 E8 NO 14 P S1487"), - cnBrowseItem(instance("instance #44"), "CE 16 B6713 X 41993"), - cnBrowseItem(instance("instance #45"), "CE 16 B6724 41993"), - cnBrowseItem(instance("instance #04"), "CE 16 D86 X 41998"), - cnBrowseItem(instance("instance #38"), "CE 210 K297 41858") - ))), - - arguments(backwardIncludingQuery, secondAnchorShelfKey, 5, new CallNumberBrowseResult() - .prev("DA 43880 O6 L75").next("DA 43880 O6 M96").items(List.of( - cnBrowseItem(instance("instance #20"), "DA 3880 O6 L75"), - cnBrowseItem(instance("instance #15"), "DA 3880 O6 L76"), - cnBrowseItem(instance("instance #05"), "DA 3880 O6 M15"), - cnBrowseItem(instance("instance #13"), "DA 3880 O6 M81"), - cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96") - ))) + // anchor call number appears in the middle of the result set + arguments(aroundQuery, BrowseOptionType.ALL, callNumbers.get(1).fullCallNumber(), 5, + cnBrowseResult(callNumbers.get(91).fullCallNumber(), callNumbers.get(68).fullCallNumber(), 100, List.of( + cnBrowseItem(callNumbers.get(91), 1), + cnBrowseItem(callNumbers.get(25), 1), + cnBrowseItem(callNumbers.get(1), 3, true), + cnBrowseItem(callNumbers.get(70), 1), + cnBrowseItem(callNumbers.get(68), 1) + ))), + + // not existed anchor call number appears in the middle of the result set + arguments(aroundQuery, BrowseOptionType.ALL, "TA357 .A78 2011", 5, + cnBrowseResult(callNumbers.get(25).fullCallNumber(), callNumbers.get(68).fullCallNumber(), 100, List.of( + cnBrowseItem(callNumbers.get(25), 1), + cnBrowseItem(callNumbers.get(1), 3), + cnEmptyBrowseItem("TA357 .A78 2011"), + cnBrowseItem(callNumbers.get(70), 1), + cnBrowseItem(callNumbers.get(68), 1) + ))), + + // anchor call number appears first in the result set + arguments(aroundQuery, BrowseOptionType.ALL, callNumbers.get(50).fullCallNumber(), 5, + cnBrowseResult(null, callNumbers.get(95).fullCallNumber(), 100, List.of( + cnBrowseItem(callNumbers.get(50), 1, true), + cnBrowseItem(callNumbers.get(97), 1), + cnBrowseItem(callNumbers.get(95), 1) + ))), + + // not existed anchor call number appears first in the result set + arguments(aroundQuery, BrowseOptionType.ALL, "0.0", 5, + cnBrowseResult(null, callNumbers.get(97).fullCallNumber(), 100, List.of( + cnEmptyBrowseItem("0.0"), + cnBrowseItem(callNumbers.get(50), 1), + cnBrowseItem(callNumbers.get(97), 1) + ))), + + // anchor call number appears last in the result set + arguments(aroundQuery, BrowseOptionType.ALL, callNumbers.get(11).fullCallNumber(), 5, + cnBrowseResult(callNumbers.get(49).fullCallNumber(), null, 100, List.of( + cnBrowseItem(callNumbers.get(49), 1), + cnBrowseItem(callNumbers.get(44), 1), + cnBrowseItem(callNumbers.get(11), 1, true) + ))), + + // not existed anchor call number appears last in the result set + arguments(aroundQuery, BrowseOptionType.ALL, "ZZ", 5, + cnBrowseResult(callNumbers.get(44).fullCallNumber(), null, 100, List.of( + cnBrowseItem(callNumbers.get(44), 1), + cnBrowseItem(callNumbers.get(11), 1), + cnEmptyBrowseItem("ZZ") + ))), + + // anchor call number appears in the middle of the result set when filtering by type + arguments(aroundQuery, BrowseOptionType.LC, callNumbers.get(46).fullCallNumber(), 5, + cnBrowseResult(callNumbers.get(66).fullCallNumber(), callNumbers.get(21).fullCallNumber(), 20, List.of( + cnBrowseItem(callNumbers.get(66), 1), + cnBrowseItem(callNumbers.get(96), 1), + cnBrowseItem(callNumbers.get(46), 1, true), + cnBrowseItem(callNumbers.get(86), 1), + cnBrowseItem(callNumbers.get(21), 2) + ))), + + // forward browsing from the middle of the result set + arguments(forwardQuery, BrowseOptionType.ALL, callNumbers.get(22).fullCallNumber(), 5, + cnBrowseResult(callNumbers.get(47).fullCallNumber(), callNumbers.get(32).fullCallNumber(), 100, List.of( + cnBrowseItem(callNumbers.get(47), 1), + cnBrowseItem(callNumbers.get(62), 1), + cnBrowseItem(callNumbers.get(67), 1), + cnBrowseItem(callNumbers.get(55), 1), + cnBrowseItem(callNumbers.get(32), 1) + ))), + + // forward browsing from the end of the result set + arguments(forwardQuery, BrowseOptionType.ALL, callNumbers.get(11).fullCallNumber(), 5, + cnBrowseResult(null, null, 100, emptyList())), + + // backward browsing from the middle of the result set + arguments(backwardQuery, BrowseOptionType.ALL, callNumbers.get(22).fullCallNumber(), 5, + cnBrowseResult(callNumbers.get(92).fullCallNumber(), callNumbers.get(90).fullCallNumber(), 100, List.of( + cnBrowseItem(callNumbers.get(92), 1), + cnBrowseItem(callNumbers.get(17), 1), + cnBrowseItem(callNumbers.get(27), 1), + cnBrowseItem(callNumbers.get(42), 1), + cnBrowseItem(callNumbers.get(90), 1) + ))), + + // backward browsing from the end of the result set + arguments(backwardQuery, BrowseOptionType.ALL, callNumbers.get(50).fullCallNumber(), 5, + cnBrowseResult(null, null, 100, emptyList())) ); } - private static Instance[] instances() { - return callNumberBrowseInstanceData().stream() - .map(BrowseCallNumberIT::instance) - .toArray(Instance[]::new); - } + private static void updateLcConfig(List typeIds) { + var config = new BrowseConfig() + .id(BrowseOptionType.LC) + .shelvingAlgorithm(ShelvingOrderAlgorithmType.LC) + .typeIds(typeIds); - @SuppressWarnings("unchecked") - private static Instance instance(List data) { - var holdingId = randomId(); - var holding = new Holding().id(holdingId).discoverySuppress(false).tenantId(TENANT_ID); + var stub = mockCallNumberTypes(okapi.wireMockServer(), typeIds.toArray(new UUID[0])); + doPut(browseConfigPath(BrowseType.CALL_NUMBER, BrowseOptionType.LC), config); + okapi.wireMockServer().removeStub(stub); + } - var items = ((List) data.get(1)).stream() - .map(callNumber -> new Item() - .tenantId(TENANT_ID) - .holdingsRecordId(holdingId) - .id(randomId()) - .discoverySuppress(false) - .effectiveCallNumberComponents(new ItemEffectiveCallNumberComponents().callNumber(callNumber)) - .effectiveShelvingOrder(getShelfKeyFromCallNumber(callNumber)) - ) - .toList(); + private static CallNumberBrowseResult cnBrowseResult(String prev, String next, int total, + List items) { + return new CallNumberBrowseResult().prev(prev).next(next).items(items).totalRecords(total); + } - return new Instance() - .id(randomId()) - .title((String) data.get(0)) - .staffSuppress(false) - .discoverySuppress(false) - .isBoundWith(false) - .shared(false) - .tenantId(TENANT_ID) - .items(items) - .holdings(List.of(holding)); + private static CallNumberBrowseItem cnEmptyBrowseItem(String callNumber) { + return new CallNumberBrowseItem().fullCallNumber(callNumber).isAnchor(true).totalRecords(0); } - private static Instance instance(String title) { - return INSTANCE_MAP.get(title); + private static CallNumberBrowseItem cnBrowseItem(CallNumberResource resource, int count) { + return cnBrowseItem(resource, count, null); } - private static List> callNumberBrowseInstanceData() { - return List.of( - List.of("instance #01", List.of("DA 3880 O6 J72")), - List.of("instance #02", List.of("DA 3880 O6 M96")), - List.of("instance #03", List.of("DA 3880 O6 L5 41955")), - List.of("instance #04", List.of("CE 16 D86 X 41998")), - List.of("instance #05", List.of("DA 3880 O6 M15")), - List.of("instance #06", List.of("DA 3880 O6 L6 V2", "DA 3880 O6 L6 V1", "DA 3880 O6 L6 V2")), - List.of("instance #07", List.of("DA 3870 H47 41975")), - List.of("instance #08", List.of("AC 11 A67 X 42000")), - List.of("instance #09", List.of("DA 3700 C95 NO 18")), - List.of("instance #10", List.of("DC 3201 B34 41972")), - List.of("instance #11", List.of("DA 3880 K56 M27 41984")), - List.of("instance #12", List.of("E 211 N52 VOL 14")), - List.of("instance #13", List.of("DA 3880 O6 M81")), - List.of("instance #14", List.of("DA 3890 A1 I72 41885")), - List.of("instance #15", List.of("DA 3880 O6 L76")), - List.of("instance #16", List.of("PR 44034 B38 41993")), - List.of("instance #17", List.of("GA 16 A63 41581")), - List.of("instance #18", List.of("AC 11 E8 NO 14 P S1487")), - List.of("instance #19", List.of("DA 3890 A2 F57 42011")), - List.of("instance #20", List.of("DA 3880 O6 L75")), - List.of("instance #21", List.of("FC 17 B89")), - List.of("instance #22", List.of("DA 3890 A2 B76 42002")), - List.of("instance #23", List.of("DB 11 A66 SUPPL NO 11")), - List.of("instance #24", List.of("DA 3900 C39 NO 11")), - List.of("instance #25", List.of("AC 11 A4 VOL 235")), - List.of("instance #26", List.of("PR 17 I55 42006")), - List.of("instance #27", List.of("E 211 A506", "F 43733 L370 41992")), - List.of("instance #28", List.of("DB 11 A31 BD 3124")), - List.of("instance #29", List.of("DA 3880 O6 D5")), - List.of("instance #30", List.of("GA 16 G32 41557 V1", "GA 16 G32 41557 V2", "GA 16 G32 41557 V3")), - List.of("instance #31", List.of("AB 14 C72 NO 220", "G 45831 S2")), - List.of("instance #32", List.of("DA 3880 O5 C3 V1", "DA 3880 O5 C3 V2", "DA 3880 O5 C3 V3")), - List.of("instance #33", List.of("E 12.11 I2 298")), - List.of("instance #34", List.of("DA 3900 C89 V1", "DA 3900 C89 V2", "DA 3900 C89 V3")), - List.of("instance #35", List.of("E 12.11 I12 288 D")), - List.of("instance #36", List.of("DA 3700 B91 L79")), - List.of("instance #37", List.of("FC 17 B89")), - List.of("instance #38", List.of("CE 210 K297 41858")), - List.of("instance #39", List.of("GA 16 D64 41548A")), - List.of("instance #40", List.of("PR 213 E5 41999")), - List.of("instance #41", List.of("DA 3870 B55 41868")), - List.of("instance #42", List.of("FA 46252 3977 12 237")), - List.of("instance #43", List.of("FA 42010 3546 256")), - List.of("instance #44", List.of("CE 16 B6713 X 41993")), - List.of("instance #45", List.of("CE 16 B6724 41993")), - List.of("instance #47", List.of("J29.29:M54/990")), - List.of("instance #46", List.of("F PR1866.S63 V.1 C.1")) - ); + private static CallNumberBrowseItem cnBrowseItem(CallNumberResource resource, int count, Boolean isAnchor) { + return new CallNumberBrowseItem() + .fullCallNumber(resource.fullCallNumber()) + .callNumber(resource.callNumber()) + .callNumberPrefix(resource.callNumberPrefix()) + .callNumberSuffix(resource.callNumberSuffix()) + .callNumberTypeId(resource.callNumberTypeId()) + .volume(resource.volume()) + .chronology(resource.chronology()) + .enumeration(resource.enumeration()) + .copyNumber(resource.copyNumber()) + .totalRecords(count) + .isAnchor(isAnchor); } + } diff --git a/src/test/java/org/folio/search/controller/BrowseCallNumberOtherIT.java b/src/test/java/org/folio/search/controller/BrowseCallNumberOtherIT.java index 54ccdefe4..b30fe8d9a 100644 --- a/src/test/java/org/folio/search/controller/BrowseCallNumberOtherIT.java +++ b/src/test/java/org/folio/search/controller/BrowseCallNumberOtherIT.java @@ -19,11 +19,11 @@ import java.util.List; import java.util.Map; import java.util.function.Function; -import org.folio.search.domain.dto.CallNumberBrowseResult; import org.folio.search.domain.dto.Holding; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.Item; import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.search.domain.dto.LegacyCallNumberBrowseResult; import org.folio.search.support.base.BaseIntegrationTest; import org.folio.search.utils.TestUtils; import org.folio.spring.testing.type.IntegrationTest; @@ -53,24 +53,24 @@ void browseByCallNumber_browsingAroundWithDisabledIntermediateValues() { var request = get(instanceCallNumberBrowsePath()) .param("query", prepareQuery("callNumber >= {value} or callNumber < {value}", "g")) .param("limit", "15").param("expandAll", "true"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - var expected = new CallNumberBrowseResult() + var expected = new LegacyCallNumberBrowseResult() .totalRecords(12).prev("43350.28").next("RAW 222").items(List.of( - cnBrowseItem(instance("instance #04"), "3350.28"), - cnBrowseItem(instance("instance #05"), "3362.82 292 220"), - cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96"), - cnBrowseItem(instance("instance #09"), "F PR1866.S63 V.1 C.1"), - cnBrowseItem(instance("instance #11"), "F-1,452"), - cnBrowseItemWithNoType(instance("instance #10"), "FA 42010 3546 256"), - cnBrowseItem(0, "g", true), - cnBrowseItem(instance("instance #12"), "G SHELF#1", "G (shelf#1)"), - cnBrowseItem(instance("instance #03"), "PICCADILLY JZ 4 C.1", "Piccadilly Jz 4 c.1"), - cnBrowseItem(instance("instance #01"), "PICKWIC JZ 9 C.1", "Pickwic Jz 9 c.1"), - cnBrowseItem(instance("instance #08"), "PIRANHA 19 _C 11"), - cnBrowseItem(instance("instance #06"), "PIROUET JAS 19035 C.1", "Pirouet JAS 19035 c.1"), - cnBrowseItem(instance("instance #07"), "RAW 22") - )); + cnBrowseItem(instance("instance #04"), "3350.28"), + cnBrowseItem(instance("instance #05"), "3362.82 292 220"), + cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96"), + cnBrowseItem(instance("instance #09"), "F PR1866.S63 V.1 C.1"), + cnBrowseItem(instance("instance #11"), "F-1,452"), + cnBrowseItemWithNoType(instance("instance #10"), "FA 42010 3546 256"), + cnBrowseItem(0, "g", true), + cnBrowseItem(instance("instance #12"), "G SHELF#1", "G (shelf#1)"), + cnBrowseItem(instance("instance #03"), "PICCADILLY JZ 4 C.1", "Piccadilly Jz 4 c.1"), + cnBrowseItem(instance("instance #01"), "PICKWIC JZ 9 C.1", "Pickwic Jz 9 c.1"), + cnBrowseItem(instance("instance #08"), "PIRANHA 19 _C 11"), + cnBrowseItem(instance("instance #06"), "PIROUET JAS 19035 C.1", "Pirouet JAS 19035 c.1"), + cnBrowseItem(instance("instance #07"), "RAW 22") + )); cleanupActual(expected); assertThat(actual).isEqualTo(expected); } @@ -82,24 +82,24 @@ void browseByCallNumber_browsingAroundWithEnabledIntermediateValues() { var request = get(instanceCallNumberBrowsePath()) .param("query", prepareQuery("callNumber >= {value} or callNumber < {value}", "g")) .param("limit", "15").param("expandAll", "true"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - var expected = new CallNumberBrowseResult() + var expected = new LegacyCallNumberBrowseResult() .totalRecords(12).prev("43350.28").next("RAW 222").items(List.of( - cnBrowseItem(instance("instance #04"), "3350.28"), - cnBrowseItem(instance("instance #05"), "3362.82 292 220"), - cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96"), - cnBrowseItem(instance("instance #09"), "F PR1866.S63 V.1 C.1"), - cnBrowseItem(instance("instance #11"), "F-1,452"), - cnBrowseItemWithNoType(instance("instance #10"), "FA 42010 3546 256"), - cnBrowseItem(0, "g", true), - cnBrowseItem(instance("instance #12"), "G SHELF#1", "G (shelf#1)"), - cnBrowseItem(instance("instance #03"), "PICCADILLY JZ 4 C.1", "Piccadilly Jz 4 c.1"), - cnBrowseItem(instance("instance #01"), "PICKWIC JZ 9 C.1", "Pickwic Jz 9 c.1"), - cnBrowseItem(instance("instance #08"), "PIRANHA 19 _C 11"), - cnBrowseItem(instance("instance #06"), "PIROUET JAS 19035 C.1", "Pirouet JAS 19035 c.1"), - cnBrowseItem(instance("instance #07"), "RAW 22") - )); + cnBrowseItem(instance("instance #04"), "3350.28"), + cnBrowseItem(instance("instance #05"), "3362.82 292 220"), + cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96"), + cnBrowseItem(instance("instance #09"), "F PR1866.S63 V.1 C.1"), + cnBrowseItem(instance("instance #11"), "F-1,452"), + cnBrowseItemWithNoType(instance("instance #10"), "FA 42010 3546 256"), + cnBrowseItem(0, "g", true), + cnBrowseItem(instance("instance #12"), "G SHELF#1", "G (shelf#1)"), + cnBrowseItem(instance("instance #03"), "PICCADILLY JZ 4 C.1", "Piccadilly Jz 4 c.1"), + cnBrowseItem(instance("instance #01"), "PICKWIC JZ 9 C.1", "Pickwic Jz 9 c.1"), + cnBrowseItem(instance("instance #08"), "PIRANHA 19 _C 11"), + cnBrowseItem(instance("instance #06"), "PIROUET JAS 19035 C.1", "Pirouet JAS 19035 c.1"), + cnBrowseItem(instance("instance #07"), "RAW 22") + )); cleanupActual(expected); assertThat(actual).isEqualTo(expected); @@ -113,9 +113,9 @@ void browseByCallNumber_browsingAroundWithNotIndexedCallNumberType() { .param("limit", "5") .param("precedingRecordsCount", "3") .param("expandAll", "true"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - var expected = new CallNumberBrowseResult() + var expected = new LegacyCallNumberBrowseResult() .totalRecords(13).prev("DA 43880 O6 M96").next("G SHELF#1").items(List.of( cnBrowseItem(instance("instance #02"), "DA 3880 O6 M96"), cnBrowseItem(instance("instance #09"), "F PR1866.S63 V.1 C.1"), @@ -136,8 +136,8 @@ private static Instance[] instances() { @SuppressWarnings("unchecked") private static Instance instance(List data) { var effectiveShelvingOrderFunction = data.size() < 3 - ? (Function) TestUtils::getShelfKeyFromCallNumber - : (Function) data.get(2); + ? (Function) TestUtils::getShelfKeyFromCallNumber + : (Function) data.get(2); var holdingId = randomId(); var holding = new Holding().id(holdingId).discoverySuppress(false).tenantId(TENANT_ID); diff --git a/src/test/java/org/folio/search/controller/BrowseCallNumberTypedIT.java b/src/test/java/org/folio/search/controller/BrowseCallNumberTypedIT.java index 54815a3c6..36bb39024 100644 --- a/src/test/java/org/folio/search/controller/BrowseCallNumberTypedIT.java +++ b/src/test/java/org/folio/search/controller/BrowseCallNumberTypedIT.java @@ -1,6 +1,5 @@ package org.folio.search.controller; -import static java.util.Collections.singletonList; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; @@ -27,11 +26,11 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.folio.search.domain.dto.CallNumberBrowseResult; import org.folio.search.domain.dto.Holding; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.Item; import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.search.domain.dto.LegacyCallNumberBrowseResult; import org.folio.search.support.base.BaseIntegrationTest; import org.folio.spring.integration.XOkapiHeaders; import org.folio.spring.testing.type.IntegrationTest; @@ -66,9 +65,9 @@ void browseByCallNumberLc_browsingAroundWhenPrecedingRecordsCountIsSpecified() { .param("limit", "5") .param("expandAll", "true") .param("precedingRecordsCount", "4"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() + assertThat(actual).isEqualTo(new LegacyCallNumberBrowseResult() .totalRecords(23).prev("DA 43880 O6 M15").next("DA 43890 A2 B76 542002").items(List.of( cnBrowseItem(instance("instance #05"), "DA 3880 O6 M15"), cnBrowseItem(instance("instance #13"), "DA 3880 O6 M81"), @@ -86,9 +85,9 @@ void browseByCallNumberDewey_browsingAroundWhenPrecedingRecordsCountIsSpecified( .param("limit", "5") .param("expandAll", "true") .param("precedingRecordsCount", "4"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() + assertThat(actual).isEqualTo(new LegacyCallNumberBrowseResult() .totalRecords(11).prev("11 CE 216 B 46713 X 541993").next("11 CE 216 B 46724 541993").items(List.of( cnBrowseItem(instance("instance #44"), "1CE 16 B6713 X 41993"), cnBrowseItem(DEWEY, "1CE 16 B6724 41993", 2, true) @@ -103,9 +102,9 @@ void browseByCallNumberDewey_invalidDeweyFormat() { .param("limit", "3") .param("expandAll", "true") .param("precedingRecordsCount", "1"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() + assertThat(actual).isEqualTo(new LegacyCallNumberBrowseResult() .totalRecords(8).prev("11 CE 3210 K 3297 541858").next("A 3123.5").items(List.of( cnBrowseItem(instance("instance #38"), "1CE 210 K297 41858"), cnBrowseItem(instance("instance #48"), "A 123.4", true), @@ -120,9 +119,9 @@ void browseByShelfKeyDewey_backward() { .param("callNumberType", "dewey") .param("limit", "5") .param("expandAll", "true"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() + assertThat(actual).isEqualTo(new LegacyCallNumberBrowseResult() .totalRecords(6).prev("11 CE 216 B 46713 X 541993").next("11 CE 3210 K 3297 541858").items(List.of( cnBrowseItem(instance("instance #44"), "1CE 16 B6713 X 41993"), cnBrowseItem(DEWEY, "1CE 16 B6724 41993", 2, null), @@ -140,9 +139,9 @@ void browseByCallNumberNlm_browsingAroundWhenPrecedingRecordsCountIsSpecified() .param("limit", "5") .param("expandAll", "true") .param("precedingRecordsCount", "4"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() + assertThat(actual).isEqualTo(new LegacyCallNumberBrowseResult() .totalRecords(4).prev("AC 211 A4 VOL 3235").next("AC 211 E8 NO 214 P S1487").items(List.of( cnBrowseItem(instance("instance #25"), "AC 11 A4 VOL 235"), cnBrowseItem(instance("instance #08"), "AC 11 A67 X 42000"), @@ -158,9 +157,9 @@ void browseByCallNumberSudoc_browsingAroundWhenPrecedingRecordsCountIsSpecified( .param("limit", "5") .param("expandAll", "true") .param("precedingRecordsCount", "4"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - assertThat(actual).isEqualTo(new CallNumberBrowseResult() + assertThat(actual).isEqualTo(new LegacyCallNumberBrowseResult() .totalRecords(5).prev("D 11.211 N52 VOL 214").next("P 11.44034 B38 541993").items(List.of( cnBrowseItem(instance("instance #12"), "D1.211 N52 VOL 14"), cnBrowseItem(instance("instance #10"), "D1.3201 B34 41972"), @@ -221,19 +220,6 @@ private static Instance instance(String title) { return INSTANCE_MAP.get(title); } - private static Instance instance(Instance instance, Item item) { - return new Instance() - .id(instance.getId()) - .title(instance.getTitle()) - .staffSuppress(instance.getStaffSuppress()) - .discoverySuppress(instance.getDiscoverySuppress()) - .isBoundWith(instance.getIsBoundWith()) - .shared(instance.getShared()) - .tenantId(instance.getTenantId()) - .items(singletonList(item)) - .holdings(instance.getHoldings()); - } - private static List> callNumberBrowseInstanceData() { return List.of( List.of("instance #01", List.of(List.of("DA 3880 O6 J72", LC.getId()))), diff --git a/src/test/java/org/folio/search/controller/BrowseCallNumberTypedIrrelevantResultIT.java b/src/test/java/org/folio/search/controller/BrowseCallNumberTypedIrrelevantResultIT.java index 66cb69240..bfdc48ed3 100644 --- a/src/test/java/org/folio/search/controller/BrowseCallNumberTypedIrrelevantResultIT.java +++ b/src/test/java/org/folio/search/controller/BrowseCallNumberTypedIrrelevantResultIT.java @@ -20,11 +20,11 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.folio.search.domain.dto.CallNumberBrowseResult; import org.folio.search.domain.dto.Holding; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.Item; import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.search.domain.dto.LegacyCallNumberBrowseResult; import org.folio.search.support.base.BaseIntegrationTest; import org.folio.spring.testing.type.IntegrationTest; import org.junit.jupiter.api.AfterAll; @@ -58,9 +58,9 @@ void browseByCallNumber_browsingAroundWithEnabledIntermediateValues() { .param("highlightMatch", "true") .param("precedingRecordsCount", "5") .param("expandAll", "true"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - var expected = new CallNumberBrowseResult() + var expected = new LegacyCallNumberBrowseResult() .totalRecords(18) .prev("3308 H972") .next("3308 H981") @@ -87,9 +87,9 @@ void browseByCallNumber_browsingAroundWithoutExpandAll() { .param("limit", "10") .param("highlightMatch", "true") .param("precedingRecordsCount", "5"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - var expected = new CallNumberBrowseResult() + var expected = new LegacyCallNumberBrowseResult() .totalRecords(4) .prev("Z 3669 R360 41975") .next("Z 3669 R360 41977") @@ -112,9 +112,9 @@ void browseByCallNumber_browsingAroundWithLowLimit() { .param("highlightMatch", "true") .param("precedingRecordsCount", "7") .param("expandAll", "true"); - var actual = parseResponse(doGet(request), CallNumberBrowseResult.class); + var actual = parseResponse(doGet(request), LegacyCallNumberBrowseResult.class); cleanupActual(actual); - var expected = new CallNumberBrowseResult() + var expected = new LegacyCallNumberBrowseResult() .prev("3308 H970") .next("3308 H981") .totalRecords(18) @@ -137,7 +137,7 @@ void browseByCallNumber_browsingAroundWithLowLimit() { assertThat(actual).isEqualTo(expected); } - private void leaveOnlyBasicProps(CallNumberBrowseResult expected) { + private void leaveOnlyBasicProps(LegacyCallNumberBrowseResult expected) { expected.getItems().forEach(i -> { Instance instance = i.getInstance(); instance.setTenantId(null); diff --git a/src/test/java/org/folio/search/controller/BrowseControllerTest.java b/src/test/java/org/folio/search/controller/BrowseControllerTest.java index 6c701ecbc..61bf988bc 100644 --- a/src/test/java/org/folio/search/controller/BrowseControllerTest.java +++ b/src/test/java/org/folio/search/controller/BrowseControllerTest.java @@ -3,17 +3,20 @@ import static java.util.Collections.emptyList; import static org.folio.search.model.types.ResourceType.AUTHORITY; import static org.folio.search.model.types.ResourceType.INSTANCE; +import static org.folio.search.model.types.ResourceType.INSTANCE_CALL_NUMBER; import static org.folio.search.model.types.ResourceType.INSTANCE_SUBJECT; import static org.folio.search.support.base.ApiEndpoints.authorityBrowsePath; import static org.folio.search.support.base.ApiEndpoints.instanceCallNumberBrowsePath; import static org.folio.search.support.base.ApiEndpoints.instanceSubjectBrowsePath; import static org.folio.search.utils.SearchUtils.CALL_NUMBER_BROWSING_FIELD; +import static org.folio.search.utils.SearchUtils.LEGACY_CALL_NUMBER_BROWSING_FIELD; import static org.folio.search.utils.SearchUtils.SHELVING_ORDER_BROWSING_FIELD; import static org.folio.search.utils.TestConstants.RESOURCE_ID; import static org.folio.search.utils.TestConstants.TENANT_ID; import static org.folio.search.utils.TestUtils.authorityBrowseItem; import static org.folio.search.utils.TestUtils.subjectBrowseItem; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -25,12 +28,15 @@ import java.util.List; import java.util.Map; import org.folio.search.domain.dto.Authority; +import org.folio.search.domain.dto.BrowseOptionType; +import org.folio.search.domain.dto.CallNumberBrowseItem; import org.folio.search.model.BrowseResult; import org.folio.search.model.service.BrowseRequest; import org.folio.search.service.browse.AuthorityBrowseService; import org.folio.search.service.browse.CallNumberBrowseService; import org.folio.search.service.browse.ClassificationBrowseService; import org.folio.search.service.browse.ContributorBrowseService; +import org.folio.search.service.browse.LegacyCallNumberBrowseService; import org.folio.search.service.browse.SubjectBrowseService; import org.folio.search.service.consortium.TenantProvider; import org.folio.search.service.setter.SearchResponsePostProcessor; @@ -57,12 +63,14 @@ class BrowseControllerTest { @MockBean private AuthorityBrowseService authorityBrowseService; @MockBean - private CallNumberBrowseService callNumberBrowseService; + private LegacyCallNumberBrowseService legacyCallNumberBrowseService; @MockBean private ContributorBrowseService contributorBrowseService; @MockBean private ClassificationBrowseService classificationBrowseService; @MockBean + private CallNumberBrowseService callNumberBrowseService; + @MockBean private TenantProvider tenantProvider; @Mock private Map, SearchResponsePostProcessor> searchResponsePostProcessors = Collections.emptyMap(); @@ -74,10 +82,10 @@ public void setUp() { } @Test - void browseInstancesByCallNumber_positive() throws Exception { + void browseInstancesByCallNumberLegacy_positive() throws Exception { var query = "callNumber > PR4034 .P7 2019"; var request = browseRequest(query); - when(callNumberBrowseService.browse(request)).thenReturn(BrowseResult.empty()); + when(legacyCallNumberBrowseService.browse(request)).thenReturn(BrowseResult.empty()); var requestBuilder = get(instanceCallNumberBrowsePath()) .queryParam("query", query) .queryParam("limit", "5") @@ -91,11 +99,11 @@ void browseInstancesByCallNumber_positive() throws Exception { } @Test - void browseInstancesByCallNumber_positive_allFields() throws Exception { + void browseInstancesByCallNumberLegacy_positive_allFields() throws Exception { var query = "callNumber > B"; var request = BrowseRequest.of(INSTANCE, TENANT_ID, - query, 20, SHELVING_ORDER_BROWSING_FIELD, CALL_NUMBER_BROWSING_FIELD, true, true, 5); - when(callNumberBrowseService.browse(request)).thenReturn(BrowseResult.empty()); + query, 20, SHELVING_ORDER_BROWSING_FIELD, LEGACY_CALL_NUMBER_BROWSING_FIELD, true, true, 5); + when(legacyCallNumberBrowseService.browse(request)).thenReturn(BrowseResult.empty()); var requestBuilder = get(instanceCallNumberBrowsePath()) .queryParam("query", query) @@ -153,7 +161,7 @@ void browseAuthoritiesByHeadingRef_positive() throws Exception { } @Test - void browseInstancesByCallNumber_negative_missingQueryParameter() throws Exception { + void browseInstancesByCallNumberLegacy_negative_missingQueryParameter() throws Exception { var requestBuilder = get(instanceCallNumberBrowsePath()) .queryParam("limit", "5") .contentType(APPLICATION_JSON) @@ -169,7 +177,7 @@ void browseInstancesByCallNumber_negative_missingQueryParameter() throws Excepti } @Test - void browseInstancesByCallNumber_negative_precedingRecordsMoreThatLimit() throws Exception { + void browseInstancesByCallNumberLegacy_negative_precedingRecordsMoreThatLimit() throws Exception { var requestBuilder = get(instanceCallNumberBrowsePath()) .queryParam("query", "callNumber > A") .queryParam("limit", "5") @@ -188,7 +196,7 @@ void browseInstancesByCallNumber_negative_precedingRecordsMoreThatLimit() throws } @Test - void browseInstancesByCallNumber_negative_precedingRecordsCountIsZero() throws Exception { + void browseInstancesByCallNumberLegacy_negative_precedingRecordsCountIsZero() throws Exception { var requestBuilder = get(instanceCallNumberBrowsePath()) .queryParam("query", "callNumber >= A or callNumber < A") .queryParam("limit", "5") @@ -196,6 +204,97 @@ void browseInstancesByCallNumber_negative_precedingRecordsCountIsZero() throws E .contentType(APPLICATION_JSON) .header(XOkapiHeaders.TENANT, TENANT_ID); + mockMvc.perform(requestBuilder) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.total_records", is(1))) + .andExpect(jsonPath("$.errors[0].type", is("ConstraintViolationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].message", is( + "browseInstancesByCallNumberLegacy.precedingRecordsCount must be greater than or equal to 1"))); + } + + @Test + void browseInstancesByCallNumber_positive() throws Exception { + var query = "fullCallNumber > PR4034 .P7 2019"; + var browseRequest = BrowseRequest.of(INSTANCE_CALL_NUMBER, TENANT_ID, BrowseOptionType.ALL, query, 5, + CALL_NUMBER_BROWSING_FIELD, null, false, true, 2); + when(callNumberBrowseService.browse(browseRequest)).thenReturn(BrowseResult.of(1, "PR3", "PR5", + List.of(new CallNumberBrowseItem().callNumber("PR4034 .P7 2019")))); + var requestBuilder = get(instanceCallNumberBrowsePath(BrowseOptionType.ALL)) + .queryParam("query", query) + .queryParam("limit", "5") + .contentType(APPLICATION_JSON) + .header(XOkapiHeaders.TENANT, TENANT_ID); + + mockMvc.perform(requestBuilder) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalRecords", is(1))) + .andExpect(jsonPath("$.items.size()", is(1))) + .andExpect(jsonPath("$.prev", is("PR3"))) + .andExpect(jsonPath("$.next", is("PR5"))) + .andExpect(jsonPath("$.items[0].callNumber", is("PR4034 .P7 2019"))); + } + + @Test + void browseInstancesByCallNumber_positive_empty() throws Exception { + var query = "fullCallNumber > PR4034 .P7 2019"; + when(callNumberBrowseService.browse(any())).thenReturn(BrowseResult.empty()); + var requestBuilder = get(instanceCallNumberBrowsePath(BrowseOptionType.ALL)) + .queryParam("query", query) + .queryParam("limit", "5") + .contentType(APPLICATION_JSON) + .header(XOkapiHeaders.TENANT, TENANT_ID); + + mockMvc.perform(requestBuilder) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalRecords", is(0))) + .andExpect(jsonPath("$.items", is(emptyList()))); + } + + @Test + void browseInstancesByCallNumber_negative_missingQueryParameter() throws Exception { + var requestBuilder = get(instanceCallNumberBrowsePath(BrowseOptionType.ALL)) + .queryParam("limit", "5") + .contentType(APPLICATION_JSON) + .header(XOkapiHeaders.TENANT, TENANT_ID); + + var expectedErrorMessage = "Required request parameter 'query' for method parameter type String is not present"; + mockMvc.perform(requestBuilder) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.total_records", is(1))) + .andExpect(jsonPath("$.errors[0].message", is(expectedErrorMessage))) + .andExpect(jsonPath("$.errors[0].type", is("MissingServletRequestParameterException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))); + } + + @Test + void browseInstancesByCallNumber_negative_precedingRecordsMoreThatLimit() throws Exception { + var requestBuilder = get(instanceCallNumberBrowsePath(BrowseOptionType.ALL)) + .queryParam("query", "callNumber > A") + .queryParam("limit", "5") + .queryParam("precedingRecordsCount", "10") + .contentType(APPLICATION_JSON) + .header(XOkapiHeaders.TENANT, TENANT_ID); + + mockMvc.perform(requestBuilder) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.total_records", is(1))) + .andExpect(jsonPath("$.errors[0].message", is("Preceding records count must be less than request limit"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("precedingRecordsCount"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is("10"))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))); + } + + @Test + void browseInstancesByCallNumber_negative_precedingRecordsCountIsZero() throws Exception { + var requestBuilder = get(instanceCallNumberBrowsePath(BrowseOptionType.ALL)) + .queryParam("query", "callNumber >= A or callNumber < A") + .queryParam("limit", "5") + .queryParam("precedingRecordsCount", "0") + .contentType(APPLICATION_JSON) + .header(XOkapiHeaders.TENANT, TENANT_ID); + mockMvc.perform(requestBuilder) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.total_records", is(1))) @@ -207,6 +306,6 @@ void browseInstancesByCallNumber_negative_precedingRecordsCountIsZero() throws E private BrowseRequest browseRequest(String query) { return BrowseRequest.of(INSTANCE, TENANT_ID, query, 5, - SHELVING_ORDER_BROWSING_FIELD, CALL_NUMBER_BROWSING_FIELD, false, true, 5 / 2); + SHELVING_ORDER_BROWSING_FIELD, LEGACY_CALL_NUMBER_BROWSING_FIELD, false, true, 5 / 2); } } diff --git a/src/test/java/org/folio/search/service/browse/BrowseContextProviderTest.java b/src/test/java/org/folio/search/service/browse/BrowseContextProviderTest.java index f37c61649..cbba56b89 100644 --- a/src/test/java/org/folio/search/service/browse/BrowseContextProviderTest.java +++ b/src/test/java/org/folio/search/service/browse/BrowseContextProviderTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.folio.search.model.types.ResourceType.INSTANCE; -import static org.folio.search.utils.SearchUtils.CALL_NUMBER_BROWSING_FIELD; +import static org.folio.search.utils.SearchUtils.LEGACY_CALL_NUMBER_BROWSING_FIELD; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.when; import static org.opensearch.index.query.QueryBuilders.boolQuery; @@ -41,7 +41,7 @@ class BrowseContextProviderTest { @Test void get_positive_forward() { var rangeQuery = "callNumber > A"; - var succeedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gt("A"); + var succeedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gt("A"); when(cqlSearchQueryConverter.convert(rangeQuery, INSTANCE)).thenReturn( searchSource().query(succeedingQuery)); @@ -55,7 +55,7 @@ void get_positive_forward() { void get_positive_forwardWithFilters() { var rangeQuery = "callNumber > A and location == locationId"; var filterQuery = termQuery("location", "locationId"); - var succeedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gt("A"); + var succeedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gt("A"); when(cqlSearchQueryConverter.convert(rangeQuery, INSTANCE)).thenReturn( searchSource().query(boolQuery().must(succeedingQuery).filter(filterQuery))); @@ -68,7 +68,7 @@ void get_positive_forwardWithFilters() { @Test void get_positive_backward() { var query = "callNumber < A"; - var precedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt("A"); + var precedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt("A"); when(cqlSearchQueryConverter.convert(query, INSTANCE)).thenReturn(searchSource().query(precedingQuery)); var actual = browseContextProvider.get(request(query)); @@ -80,8 +80,8 @@ void get_positive_backward() { @Test void get_positive_aroundIncluding() { var query = "callNumber < A or callNumber > A"; - var precedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt("A"); - var succeedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte("A"); + var precedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt("A"); + var succeedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte("A"); when(cqlSearchQueryConverter.convert(query, INSTANCE)).thenReturn( searchSource().query(boolQuery().should(precedingQuery).should(succeedingQuery))); @@ -96,8 +96,8 @@ void get_positive_aroundIncluding() { @Test void get_positive_aroundIncludingReverseDirections() { var query = "callNumber > A or callNumber < A"; - var precedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt("A"); - var succeedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte("A"); + var precedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt("A"); + var succeedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte("A"); when(cqlSearchQueryConverter.convert(query, INSTANCE)).thenReturn( searchSource().query(boolQuery().should(succeedingQuery).should(precedingQuery))); @@ -112,8 +112,8 @@ void get_positive_aroundIncludingReverseDirections() { @Test void get_positive_aroundWithFilters() { var query = "(callNumber > A or callNumber < A) and location == locationId"; - var precedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt("A"); - var succeedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte("A"); + var precedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt("A"); + var succeedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte("A"); var filterQuery = termQuery("location", "locationId"); when(cqlSearchQueryConverter.convert(query, INSTANCE)).thenReturn(searchSource().query(boolQuery() .must(boolQuery().should(succeedingQuery).should(precedingQuery)).filter(filterQuery))); @@ -130,7 +130,7 @@ void get_positive_aroundWithFilters() { @Test void get_negative_forwardWithSorting() { var query = "callNumber > A sortBy title"; - var esQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gt("A"); + var esQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gt("A"); when(cqlSearchQueryConverter.convert(query, INSTANCE)) .thenReturn(searchSource().query(esQuery).sort("title")); @@ -149,8 +149,8 @@ void get_negative_forwardWithSorting() { @Test void get_negative_aroundWithDifferentAnchors() { var query = "callNumber > A or callNumber > B"; - var precedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt("A"); - var succeedingQuery = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gt("B"); + var precedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt("A"); + var succeedingQuery = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gt("B"); when(cqlSearchQueryConverter.convert(query, INSTANCE)).thenReturn(searchSource() .query(boolQuery().should(precedingQuery).should(succeedingQuery))); @@ -186,29 +186,32 @@ void get_negative_parameterized(String query, QueryBuilder esQuery) { public static Stream invalidQueriesDataSource() { var filterQuery = termQuery("location", "locationId"); return Stream.of( - arguments("callNumber == A", termQuery(CALL_NUMBER_BROWSING_FIELD, "A")), + arguments("callNumber == A", termQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD, "A")), arguments("unknown > A", rangeQuery("unknown").gt("A")), arguments("unknown > A and unknown < A", boolQuery() .must(rangeQuery("unknown").lt("A")).must(rangeQuery("unknown").gt("A"))), arguments("callNumber > A or callNumber == A", boolQuery() - .should(rangeQuery(CALL_NUMBER_BROWSING_FIELD).gt("A")).should(termQuery(CALL_NUMBER_BROWSING_FIELD, "A"))), + .should(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gt("A")).should(termQuery( + LEGACY_CALL_NUMBER_BROWSING_FIELD, "A"))), arguments("callNumber > A or callNumber > A", boolQuery() - .should(rangeQuery(CALL_NUMBER_BROWSING_FIELD).gt("A")).should(rangeQuery(CALL_NUMBER_BROWSING_FIELD).gt("A"))), + .should(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gt("A")).should(rangeQuery( + LEGACY_CALL_NUMBER_BROWSING_FIELD).gt("A"))), arguments("callNumber < A or callNumber < A", boolQuery() - .should(rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt("A")).should(rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt("A"))), + .should(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt("A")).should(rangeQuery( + LEGACY_CALL_NUMBER_BROWSING_FIELD).lt("A"))), arguments("callNumber == (1 or 2) and location == locationId", boolQuery() - .must(boolQuery().should(termQuery(CALL_NUMBER_BROWSING_FIELD, "1")) - .should(termQuery(CALL_NUMBER_BROWSING_FIELD, "2"))).filter(filterQuery)), + .must(boolQuery().should(termQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD, "1")) + .should(termQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD, "2"))).filter(filterQuery)), arguments("callNumber == 1 and location == locationId", boolQuery() - .must(termQuery(CALL_NUMBER_BROWSING_FIELD, "1")).filter(filterQuery)), + .must(termQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD, "1")).filter(filterQuery)), arguments("callNumber == (1 or 2)", boolQuery().must(boolQuery().must(boolQuery() - .should(termQuery(CALL_NUMBER_BROWSING_FIELD, "1")) - .should(termQuery(CALL_NUMBER_BROWSING_FIELD, "2"))))) + .should(termQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD, "1")) + .should(termQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD, "2"))))) ); } private static BrowseRequest request(String query) { - return BrowseRequest.builder().targetField(CALL_NUMBER_BROWSING_FIELD) + return BrowseRequest.builder().targetField(LEGACY_CALL_NUMBER_BROWSING_FIELD) .limit(20).precedingRecordsCount(9).resource(INSTANCE).query(query).build(); } } diff --git a/src/test/java/org/folio/search/service/browse/CallNumberBrowseResultConverterTest.java b/src/test/java/org/folio/search/service/browse/CallNumberBrowseResultConverterTest.java index 4e08e91c6..1a413d638 100644 --- a/src/test/java/org/folio/search/service/browse/CallNumberBrowseResultConverterTest.java +++ b/src/test/java/org/folio/search/service/browse/CallNumberBrowseResultConverterTest.java @@ -4,7 +4,7 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.folio.search.domain.dto.TenantConfiguredFeature.BROWSE_CN_INTERMEDIATE_VALUES; -import static org.folio.search.utils.SearchUtils.CALL_NUMBER_BROWSING_FIELD; +import static org.folio.search.utils.SearchUtils.LEGACY_CALL_NUMBER_BROWSING_FIELD; import static org.folio.search.utils.TestUtils.OBJECT_MAPPER; import static org.folio.search.utils.TestUtils.cnBrowseItem; import static org.folio.search.utils.TestUtils.getShelfKeyFromCallNumber; @@ -25,6 +25,7 @@ import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.Item; import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.search.domain.dto.LegacyCallNumberBrowseItem; import org.folio.search.model.BrowseResult; import org.folio.search.model.service.BrowseContext; import org.folio.search.model.service.BrowseRequest; @@ -224,19 +225,23 @@ private static Stream testDataProvider() { } private static BrowseContext forwardIncludingContext() { - return BrowseContext.builder().anchor("A").succeedingQuery(rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte("A")).build(); + return BrowseContext.builder().anchor("A") + .succeedingQuery(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte("A")).build(); } private static BrowseContext forwardContext() { - return BrowseContext.builder().anchor("A").succeedingQuery(rangeQuery(CALL_NUMBER_BROWSING_FIELD).gt("A")).build(); + return BrowseContext.builder().anchor("A") + .succeedingQuery(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gt("A")).build(); } private static BrowseContext backwardContext() { - return BrowseContext.builder().anchor("F").precedingQuery(rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt("F")).build(); + return BrowseContext.builder().anchor("F") + .precedingQuery(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt("F")).build(); } private static BrowseContext backwardIncludingContext() { - return BrowseContext.builder().anchor("F").precedingQuery(rangeQuery(CALL_NUMBER_BROWSING_FIELD).lte("F")).build(); + return BrowseContext.builder().anchor("F") + .precedingQuery(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lte("F")).build(); } private static List searchHits(String... sortShelfKey) { @@ -254,11 +259,11 @@ private static SearchHit searchHit(String sortCallnumber, Instance instance) { return searchHit; } - private static CallNumberBrowseItem browseItem(String shelfKey) { + private static LegacyCallNumberBrowseItem browseItem(String shelfKey) { return cnBrowseItem(instance(shelfKey), shelfKey); } - private static List browseItems(String... shelfKeys) { + private static List browseItems(String... shelfKeys) { return stream(shelfKeys).map(CallNumberBrowseResultConverterTest::browseItem).toList(); } diff --git a/src/test/java/org/folio/search/service/browse/CallNumberBrowseServiceTest.java b/src/test/java/org/folio/search/service/browse/LegacyCallNumberBrowseServiceTest.java similarity index 88% rename from src/test/java/org/folio/search/service/browse/CallNumberBrowseServiceTest.java rename to src/test/java/org/folio/search/service/browse/LegacyCallNumberBrowseServiceTest.java index 13ce31264..7691bad23 100644 --- a/src/test/java/org/folio/search/service/browse/CallNumberBrowseServiceTest.java +++ b/src/test/java/org/folio/search/service/browse/LegacyCallNumberBrowseServiceTest.java @@ -5,7 +5,7 @@ import static java.util.Collections.singletonList; import static org.apache.lucene.search.TotalHits.Relation.EQUAL_TO; import static org.assertj.core.api.Assertions.assertThat; -import static org.folio.search.utils.SearchUtils.CALL_NUMBER_BROWSING_FIELD; +import static org.folio.search.utils.SearchUtils.LEGACY_CALL_NUMBER_BROWSING_FIELD; import static org.folio.search.utils.SearchUtils.SHELVING_ORDER_BROWSING_FIELD; import static org.folio.search.utils.TestConstants.TENANT_ID; import static org.folio.search.utils.TestUtils.cleanupActual; @@ -25,17 +25,16 @@ import org.folio.search.configuration.properties.SearchConfigurationProperties; import org.folio.search.cql.CqlSearchQueryConverter; import org.folio.search.cql.EffectiveShelvingOrderTermProcessor; -import org.folio.search.domain.dto.CallNumberBrowseItem; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.Item; import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.search.domain.dto.LegacyCallNumberBrowseItem; import org.folio.search.model.BrowseResult; import org.folio.search.model.service.BrowseContext; import org.folio.search.model.service.BrowseRequest; import org.folio.search.model.types.CallNumberType; import org.folio.search.model.types.ResourceType; import org.folio.search.repository.SearchRepository; -import org.folio.search.utils.CallNumberUtils; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -55,11 +54,11 @@ @UnitTest @Disabled @ExtendWith(MockitoExtension.class) -class CallNumberBrowseServiceTest { +class LegacyCallNumberBrowseServiceTest { private static final String ANCHOR = "B"; @InjectMocks - private CallNumberBrowseService callNumberBrowseService; + private LegacyCallNumberBrowseService legacyCallNumberBrowseService; @Mock private SearchRepository searchRepository; @@ -89,18 +88,12 @@ class CallNumberBrowseServiceTest { @BeforeEach void setUp() { - callNumberBrowseService.setBrowseContextProvider(browseContextProvider); + legacyCallNumberBrowseService.setBrowseContextProvider(browseContextProvider); lenient().when(cqlSearchQueryConverter.convertToTermNode(anyString(), any())) .thenReturn(new CQLTermNode(null, null, "B")); lenient().when(shelvingOrderProcessor.getSearchTerms(ANCHOR)).thenReturn(newArrayList(ANCHOR)); } - @Test - void name() { - System.out.println(CallNumberUtils.getCallNumberAsLong("11.4", 2)); - System.out.println(CallNumberUtils.getCallNumberAsLong("", 2)); - } - @Test void browse_positive_around() { var request = request("callNumber >= B or callNumber < B", true); @@ -109,7 +102,7 @@ void browse_positive_around() { BrowseResult.of(4, browseItems("A1", "A2", "A3", "A4")), BrowseResult.of(7, browseItems("C1", "C2", "C3", "C4", "C5", "C6", "C7"))); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(11, "A3", "C2", List.of( @@ -129,7 +122,7 @@ void browse_positive_aroundWithFoundAnchor() { BrowseResult.of(1, browseItems("A 11", "A 12")), BrowseResult.of(2, browseItems("B", "C 11"))); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(3, List.of( @@ -150,7 +143,7 @@ void browse_positive_around_additionalRequest_when_emptyPrecedingResults() { prepareMockForAdditionalRequest(request, contextAroundIncluding(), additionalPrecedingResult); when(searchConfig.getMaxBrowseRequestOffset()).thenReturn(500L); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(2, List.of( @@ -165,7 +158,7 @@ void browse_positive_around_emptySucceedingResults() { prepareMockForBrowsingAround(request, contextAroundIncluding(), BrowseResult.of(1, browseItems("A 11", "A 12")), BrowseResult.empty()); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(1, List.of( @@ -183,7 +176,7 @@ void browse_positive_around_noHighlightMatch() { BrowseResult.of(1, browseItems("A 11", "A 12")), BrowseResult.of(1, browseItems("C 11"))); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(2, List.of( @@ -206,7 +199,7 @@ void browse_positive_around_highlightMatchWithSuffix(String callNumber) { BrowseResult.empty(), BrowseResult.of(1, List.of(browseItemWithSuffix("B", "2005")))); - var actual = callNumberBrowseService.browse(request).getRecords().get(0); + var actual = legacyCallNumberBrowseService.browse(request).getRecords().get(0); assertThat(actual.getInstance()).isNotNull(); assertThat(actual.getIsAnchor()).isTrue(); @@ -216,14 +209,14 @@ void browse_positive_around_highlightMatchWithSuffix(String callNumber) { void browse_positive_around_noResults() { var request = request("callNumber >= B or callNumber < B", false); prepareMockForBrowsingAround(request, contextAroundIncluding(), BrowseResult.empty(), BrowseResult.empty()); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); assertThat(actual).isEqualTo(BrowseResult.empty()); } @Test void browse_positive_forward() { var request = request("callNumber >= B", false); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); var context = BrowseContext.builder().succeedingQuery(query).succeedingLimit(5).anchor(ANCHOR).build(); when(shelvingOrderProcessor.getSearchTerm(any(), any())).thenReturn(context.getAnchor()); @@ -233,7 +226,7 @@ void browse_positive_forward() { when(browseResultConverter.convert(succeedingResponse, context, request, true)).thenReturn( BrowseResult.of(2, browseItems("C1", "C2"))); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(2, "C1", null, List.of( @@ -243,7 +236,7 @@ void browse_positive_forward() { @Test void browse_positive_forwardMultipleAnchors() { var request = request("callNumber >= B", false); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); var multiAnchorContext = BrowseContext.builder().succeedingQuery(query).succeedingLimit(5).anchor(ANCHOR) .build(); var context = BrowseContext.builder().succeedingQuery(query).succeedingLimit(5).anchor(ANCHOR).build(); @@ -255,7 +248,7 @@ void browse_positive_forwardMultipleAnchors() { when(browseResultConverter.convert(succeedingResponse, context, request, true)).thenReturn( BrowseResult.of(1, browseItems("B"))); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(1, "B", null, List.of( @@ -265,11 +258,11 @@ void browse_positive_forwardMultipleAnchors() { @Test void browse_positive_emptyAnchor() { var request = request("callNumber >= []", false); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); var context = BrowseContext.builder().succeedingQuery(query).succeedingLimit(5).anchor("").build(); when(browseContextProvider.get(request)).thenReturn(context); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); assertThat(actual).isEqualTo(BrowseResult.empty()); } @@ -311,7 +304,7 @@ void browse_positive_multipleAnchors() { when(browseResultConverter.convert(succeedingResponse, contextForAnchorInResponse, request, true)) .thenReturn(succeedingResult); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(2, List.of( @@ -324,7 +317,7 @@ void browse_positive_multipleAnchors() { @Test void browse_positive_forwardWithNextValue() { var request = request("callNumber >= B", false); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); var context = BrowseContext.builder().succeedingQuery(query).succeedingLimit(2).anchor(ANCHOR).build(); when(shelvingOrderProcessor.getSearchTerm(any(), any())).thenReturn(context.getAnchor()); @@ -334,7 +327,7 @@ void browse_positive_forwardWithNextValue() { when(browseResultConverter.convert(succeedingResponse, context, request, true)).thenReturn( BrowseResult.of(5, browseItems("C1", "C2", "C3", "C4", "C5"))); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(5, "C1", "C2", List.of( @@ -344,7 +337,7 @@ void browse_positive_forwardWithNextValue() { @Test void browse_positive_forwardZeroResults() { var request = request("callNumber >= B", false); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte(ANCHOR); var context = BrowseContext.builder().succeedingQuery(query).succeedingLimit(5).anchor(ANCHOR).build(); when(shelvingOrderProcessor.getSearchTerm(any(), any())).thenReturn(context.getAnchor()); @@ -353,7 +346,7 @@ void browse_positive_forwardZeroResults() { when(searchRepository.search(request, succeedingQuery)).thenReturn(succeedingResponse); when(browseResultConverter.convert(succeedingResponse, context, request, true)).thenReturn(BrowseResult.empty()); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); assertThat(actual).isEqualTo(BrowseResult.empty()); } @@ -361,7 +354,7 @@ void browse_positive_forwardZeroResults() { @Test void browse_positive_backward() { var request = request("callNumber < B", false); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt(ANCHOR); var context = BrowseContext.builder().precedingQuery(query).precedingLimit(5).anchor(ANCHOR).build(); when(shelvingOrderProcessor.getSearchTerm(any(), any())).thenReturn(context.getAnchor()); @@ -371,7 +364,7 @@ void browse_positive_backward() { when(browseResultConverter.convert(precedingResponse, context, request, false)).thenReturn( BrowseResult.of(2, browseItems("A1", "A2"))); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(2, null, "A2", List.of( @@ -393,7 +386,7 @@ void browse_positive_backwardShelvingOrderTyped() { when(searchRepository.search(request, precedingQuery)).thenReturn(precedingResponse); when(browseResultConverter.convert(precedingResponse, context, request, false)).thenReturn(browseResult); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); assertThat(actual).isEqualTo(browseResult); verifyNoInteractions(shelvingOrderProcessor); @@ -402,7 +395,7 @@ void browse_positive_backwardShelvingOrderTyped() { @Test void browse_positive_backwardWithPrevValue() { var request = request("callNumber < B", false); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt(ANCHOR); var context = BrowseContext.builder().precedingQuery(query).precedingLimit(2).anchor(ANCHOR).build(); when(shelvingOrderProcessor.getSearchTerm(any(), any())).thenReturn(context.getAnchor()); @@ -412,7 +405,7 @@ void browse_positive_backwardWithPrevValue() { when(browseResultConverter.convert(precedingResponse, context, request, false)).thenReturn( BrowseResult.of(5, browseItems("A1", "A2", "A3", "A4", "A5"))); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(5, "A4", "A5", List.of( @@ -422,7 +415,7 @@ void browse_positive_backwardWithPrevValue() { @Test void browse_positive_backwardZeroResults() { var request = request("callNumber < B", false); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt(ANCHOR); var context = BrowseContext.builder().precedingQuery(query).precedingLimit(5).anchor(ANCHOR).build(); when(shelvingOrderProcessor.getSearchTerm(any(), any())).thenReturn(context.getAnchor()); @@ -431,7 +424,7 @@ void browse_positive_backwardZeroResults() { when(searchRepository.search(request, precedingQuery)).thenReturn(precedingResponse); when(browseResultConverter.convert(precedingResponse, context, request, false)).thenReturn(BrowseResult.empty()); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); assertThat(actual).isEqualTo(BrowseResult.empty()); } @@ -439,7 +432,7 @@ void browse_positive_backwardZeroResults() { @Test void browse_positive_backwardWithIrrelevantCallNumberTypes() { var request = request("typedCallNumber < B", false, "lc"); - var query = rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt(ANCHOR); + var query = rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt(ANCHOR); var context = BrowseContext.builder().precedingQuery(query).precedingLimit(5).anchor(ANCHOR).build(); var browseItems = browseItems("A1", "A2"); browseItems.get(0).getInstance().getItems().get(0).getEffectiveCallNumberComponents() @@ -455,7 +448,7 @@ void browse_positive_backwardWithIrrelevantCallNumberTypes() { when(searchRepository.search(request, precedingQuery)).thenReturn(precedingResponse); when(browseResultConverter.convert(precedingResponse, context, request, false)).thenReturn(browseResult); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); assertThat(actual).isEqualTo(expected); } @@ -488,7 +481,7 @@ void browse_positive_emptySucceedingResults() { when(browseResultConverter.convert(succeedingResponse, context, request, true)) .thenReturn(succeedingResult); - var actual = callNumberBrowseService.browse(request); + var actual = legacyCallNumberBrowseService.browse(request); cleanupActual(actual); assertThat(actual).isEqualTo(BrowseResult.of(3, List.of( @@ -501,8 +494,8 @@ void browse_positive_emptySucceedingResults() { } private void prepareMockForBrowsingAround(BrowseRequest request, BrowseContext context, - BrowseResult precedingResult, - BrowseResult succeedingResult) { + BrowseResult precedingResult, + BrowseResult succeedingResult) { when(browseContextProvider.get(request)).thenReturn(context); when(shelvingOrderProcessor.getSearchTerm(any(), any())).thenReturn(context.getAnchor()); when(browseQueryProvider.get(request, context, false)).thenReturn(precedingQuery); @@ -517,7 +510,7 @@ private void prepareMockForBrowsingAround(BrowseRequest request, BrowseContext c } private void prepareMockForAdditionalRequest(BrowseRequest request, BrowseContext context, - BrowseResult additionalResult) { + BrowseResult additionalResult) { var mockHits = mock(SearchHits.class); when(precedingQuery.from()).thenReturn(1); when(precedingQuery.from(anyInt())).thenReturn(precedingQuery); @@ -550,8 +543,8 @@ private static BrowseContext contextAroundIncluding() { private static BrowseContext contextAroundIncluding(String anchor, String searchTerm) { return BrowseContext.builder() - .precedingQuery(rangeQuery(CALL_NUMBER_BROWSING_FIELD).lt(searchTerm)) - .succeedingQuery(rangeQuery(CALL_NUMBER_BROWSING_FIELD).gte(searchTerm)) + .precedingQuery(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).lt(searchTerm)) + .succeedingQuery(rangeQuery(LEGACY_CALL_NUMBER_BROWSING_FIELD).gte(searchTerm)) .precedingLimit(2) .succeedingLimit(3) .anchor(anchor) @@ -559,11 +552,11 @@ private static BrowseContext contextAroundIncluding(String anchor, String search } private static BrowseRequest request(String query, boolean highlightMatch) { - return request(query, CALL_NUMBER_BROWSING_FIELD, highlightMatch, null, 1, 2); + return request(query, LEGACY_CALL_NUMBER_BROWSING_FIELD, highlightMatch, null, 1, 2); } private static BrowseRequest request(String query, boolean highlightMatch, String callNumberType) { - return request(query, CALL_NUMBER_BROWSING_FIELD, highlightMatch, callNumberType, 1, 2); + return request(query, LEGACY_CALL_NUMBER_BROWSING_FIELD, highlightMatch, callNumberType, 1, 2); } private static BrowseRequest request(String query, String browsingField, boolean highlightMatch, @@ -572,7 +565,7 @@ private static BrowseRequest request(String query, String browsingField, boolean } private static BrowseRequest request(String query, boolean highlightMatch, int precedingCount, int limit) { - return request(query, CALL_NUMBER_BROWSING_FIELD, highlightMatch, null, precedingCount, limit); + return request(query, LEGACY_CALL_NUMBER_BROWSING_FIELD, highlightMatch, null, precedingCount, limit); } private static BrowseRequest request(String query, String browsingField, boolean highlightMatch, @@ -588,27 +581,27 @@ private static BrowseRequest request(String query, String browsingField, boolean .build(); } - private static CallNumberBrowseItem browseItem(String callNumber) { - return new CallNumberBrowseItem() + private static LegacyCallNumberBrowseItem browseItem(String callNumber) { + return new LegacyCallNumberBrowseItem() .fullCallNumber(callNumber) .shelfKey(getShelfKeyFromCallNumber(callNumber)) .instance(instance(callNumber)) .totalRecords(1); } - private static CallNumberBrowseItem browseItemWithSuffix(String callNumber, String suffix) { + private static LegacyCallNumberBrowseItem browseItemWithSuffix(String callNumber, String suffix) { var instance = instance(callNumber); instance.getItems().get(0).getEffectiveCallNumberComponents().setSuffix(suffix); - return new CallNumberBrowseItem() + return new LegacyCallNumberBrowseItem() .fullCallNumber(callNumber + " " + suffix) .shelfKey(getShelfKeyFromCallNumber(callNumber) + " " + suffix) .instance(instance) .totalRecords(1); } - private static List browseItems(String... shelfKeys) { + private static List browseItems(String... shelfKeys) { return stream(shelfKeys) - .map(CallNumberBrowseServiceTest::browseItem) + .map(LegacyCallNumberBrowseServiceTest::browseItem) .toList(); } } diff --git a/src/test/java/org/folio/search/service/converter/preprocessor/extractor/CallNumberResourceExtractorTest.java b/src/test/java/org/folio/search/service/converter/preprocessor/extractor/CallNumberResourceExtractorTest.java index 1fb6a9ac3..d0f4af0d7 100644 --- a/src/test/java/org/folio/search/service/converter/preprocessor/extractor/CallNumberResourceExtractorTest.java +++ b/src/test/java/org/folio/search/service/converter/preprocessor/extractor/CallNumberResourceExtractorTest.java @@ -17,6 +17,7 @@ import java.util.function.Supplier; import org.folio.search.domain.dto.TenantConfiguredFeature; import org.folio.search.service.FeatureConfigService; +import org.folio.search.service.consortium.ConsortiumTenantProvider; import org.folio.search.service.converter.preprocessor.extractor.impl.CallNumberResourceExtractor; import org.folio.search.service.reindex.jdbc.CallNumberRepository; import org.folio.search.utils.JsonConverter; @@ -35,6 +36,8 @@ class CallNumberResourceExtractorTest extends ChildResourceExtractorTestBase { private CallNumberRepository repository; @Mock private FeatureConfigService featureConfigService; + @Mock + private ConsortiumTenantProvider tenantProvider; private CallNumberResourceExtractor extractor; @@ -47,7 +50,7 @@ protected int getExpectedEntitiesSize() { void setUp() { extractor = new CallNumberResourceExtractor(repository, new JsonConverter(new ObjectMapper()), - featureConfigService); + featureConfigService, tenantProvider); } @Test diff --git a/src/test/java/org/folio/search/support/base/ApiEndpoints.java b/src/test/java/org/folio/search/support/base/ApiEndpoints.java index da6f695b0..640485eb9 100644 --- a/src/test/java/org/folio/search/support/base/ApiEndpoints.java +++ b/src/test/java/org/folio/search/support/base/ApiEndpoints.java @@ -89,6 +89,10 @@ public static String instanceCallNumberBrowsePath() { return "/browse/call-numbers/instances"; } + public static String instanceCallNumberBrowsePath(BrowseOptionType optionType) { + return "/browse/call-numbers/" + optionType.getValue() + "/instances"; + } + public static String instanceSubjectBrowsePath() { return "/browse/subjects/instances"; } diff --git a/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java b/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java index acb7d92e2..8782bf437 100644 --- a/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java +++ b/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java @@ -7,6 +7,7 @@ import static org.folio.search.utils.TestUtils.asJsonString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Map; @@ -75,6 +76,20 @@ public static ResultActions doGet(MockHttpServletRequestBuilder request, String .andExpect(status().isOk()); } + @SneakyThrows + public static ResultActions tryPut(String uri, String tenantHeader, Object body) { + return mockMvc.perform(put(uri) + .content(asJsonString(body)) + .headers(defaultHeaders(tenantHeader)) + .accept("application/json;charset=UTF-8")); + } + + @SneakyThrows + public static ResultActions doPut(String uri, String tenantHeader, Object body) { + return tryPut(uri, tenantHeader, body) + .andExpect(status().isOk()); + } + @SneakyThrows public static ResultActions tryPost(String uri, Object body) { return tryPost(uri, MEMBER_TENANT_ID, body); diff --git a/src/test/java/org/folio/search/utils/CallNumberTestData.java b/src/test/java/org/folio/search/utils/CallNumberTestData.java new file mode 100644 index 000000000..fbe1ac92c --- /dev/null +++ b/src/test/java/org/folio/search/utils/CallNumberTestData.java @@ -0,0 +1,190 @@ +package org.folio.search.utils; + +import static org.folio.search.utils.CallNumberUtils.calculateFullCallNumber; +import static org.folio.search.utils.TestConstants.TENANT_ID; +import static org.folio.search.utils.TestUtils.randomId; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.folio.search.domain.dto.Holding; +import org.folio.search.domain.dto.Instance; +import org.folio.search.domain.dto.Item; +import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.search.model.index.CallNumberResource; +import org.junit.jupiter.params.shadow.com.univocity.parsers.common.record.Record; +import org.junit.jupiter.params.shadow.com.univocity.parsers.csv.CsvParser; +import org.junit.jupiter.params.shadow.com.univocity.parsers.csv.CsvParserSettings; + +@UtilityClass +public class CallNumberTestData { + + public static List callNumbers() { + var callNumbersCsvPath = "/samples/cn-browse/call-numbers.csv"; + var locations = locations(); + return readCsvEntities(callNumbersCsvPath, callNumberMapper(locations)); + } + + public static Map locations() { + var locationsPath = "/samples/cn-browse/locations.csv"; + return readCsvEntities(locationsPath, csvRecord -> Map.entry( + Integer.parseInt(csvRecord.getString(0)), + csvRecord.getString(1) + )).stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + public static List instances() { + var instancesPath = "/samples/cn-browse/instances-with-call-numbers.csv"; + var callNumbers = callNumbers(); + return readCsvEntities(instancesPath, instanceMapper(callNumbers)); + } + + private static Function instanceMapper(List callNumbers) { + return csvRecord -> { + var id = csvRecord.getString(InstanceCsvHeader.ID.getHeader()); + var callNumberList = Arrays.stream(csvRecord.getString(InstanceCsvHeader.CALL_NUMBER_NUMS.getHeader()).split(";")) + .map(callNumberId -> callNumbers.stream() + .filter(cn -> cn.callNumber().id().equals(callNumberId)) + .findFirst()) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); + return instance(id, callNumberList); + }; + } + + private static Instance instance(String instanceNum, List callNumbers) { + var holdingId = randomId(); + var holding = new Holding().id(holdingId).tenantId(TENANT_ID); + + var items = callNumbers.stream() + .map(callNumberResource -> { + var callNumber = callNumberResource.callNumber(); + var locationId = callNumberResource.locationId(); + return new Item() + .id(randomId()) + .tenantId(TENANT_ID) + .holdingsRecordId(holdingId) + .effectiveLocationId(locationId) + .volume(callNumber.volume()) + .enumeration(callNumber.enumeration()) + .chronology(callNumber.chronology()) + .copyNumber(callNumber.copyNumber()) + .effectiveCallNumberComponents(new ItemEffectiveCallNumberComponents() + .callNumber(callNumber.callNumber()) + .typeId(callNumber.callNumberTypeId()) + .prefix(callNumber.callNumberPrefix()) + .suffix(callNumber.callNumberSuffix()) + ); + } + ).toList(); + + return new Instance() + .id(randomId()) + .title("Instance " + instanceNum) + .tenantId(TENANT_ID) + .items(items) + .holdings(List.of(holding)); + } + + private static Function callNumberMapper(Map locations) { + return csvRecord -> { + var id = csvRecord.getString(CallNumberCsvHeader.ID.getHeader()); + var callNumber = csvRecord.getString(CallNumberCsvHeader.CALL_NUMBER.getHeader()); + var typeId = CallNumberTypeId.getIdByName(csvRecord.getString(CallNumberCsvHeader.TYPE.getHeader())); + var prefix = csvRecord.getString(CallNumberCsvHeader.PREFIX.getHeader()); + var suffix = csvRecord.getString(CallNumberCsvHeader.SUFFIX.getHeader()); + var volume = csvRecord.getString(CallNumberCsvHeader.VOLUME.getHeader()); + var enumeration = csvRecord.getString(CallNumberCsvHeader.ENUMERATION.getHeader()); + var chronology = csvRecord.getString(CallNumberCsvHeader.CHRONOLOGY.getHeader()); + var copyNumber = csvRecord.getString(CallNumberCsvHeader.COPY_NUMBER.getHeader()); + var fullCallNumber = calculateFullCallNumber(callNumber, volume, enumeration, chronology, copyNumber, suffix); + var callNumberResource = new CallNumberResource(id, fullCallNumber, callNumber, prefix, suffix, typeId, + volume, enumeration, chronology, copyNumber, null); + var locationNum = csvRecord.getString(CallNumberCsvHeader.LOCATION.getHeader()); + var locationId = locations.get(Integer.parseInt(locationNum)); + return new CallNumberTestDataRecord(callNumberResource, locationId); + }; + } + + @SneakyThrows + private static List readCsvEntities(String filePath, Function mapper) { + var settings = new CsvParserSettings(); + settings.setHeaderExtractionEnabled(true); + var csvParser = new CsvParser(settings); + try (InputStream inputStream = CallNumberTestData.class.getResourceAsStream(filePath)) { + csvParser.beginParsing(inputStream); + return csvParser.parseAllRecords().stream() + .map(mapper) + .toList(); + } + } + + @Getter + private enum InstanceCsvHeader { + ID("Num"), + CALL_NUMBER_NUMS("Call Number Nums"); + + private final String header; + + InstanceCsvHeader(String header) { + this.header = header; + } + } + + @Getter + private enum CallNumberCsvHeader { + ID("Num"), + CALL_NUMBER("Call Number"), + TYPE("Type"), + PREFIX("Prefix"), + SUFFIX("Suffix"), + VOLUME("Volume"), + ENUMERATION("Enumeration"), + CHRONOLOGY("Chronology"), + COPY_NUMBER("Copy Number"), + LOCATION("Location Num"); + + private final String header; + + CallNumberCsvHeader(String header) { + this.header = header; + } + } + + @Getter + public enum CallNumberTypeId { + LC("LC", "cbc422b0-b71a-4d2d-9a2a-9fcc56a57a3e"), + DEWEY("DEWEY", "0b5d15ad-1738-4e9e-a9c1-e972e95ce71c"), + SUDOC("SUDOC", "6b368b19-0d18-4689-8d15-cf904e15b3f0"), + OTHER("OTHER", "cf74a451-41ad-49aa-aa2b-21d2d2f2e235"), + NLM("NLM", "530b84ea-4965-4cda-9d9e-6a5ef91fd21e"); + + private final String name; + private final String id; + + CallNumberTypeId(String name, String id) { + this.name = name; + this.id = id; + } + + public static String getIdByName(String name) { + for (var value : values()) { + if (value.name.equalsIgnoreCase(name)) { + return value.id; + } + } + return null; + } + } + + public record CallNumberTestDataRecord(CallNumberResource callNumber, String locationId) { + } +} diff --git a/src/test/java/org/folio/search/utils/CallNumberUtilsTest.java b/src/test/java/org/folio/search/utils/CallNumberUtilsTest.java index 2a5a2ae82..60d5b3606 100644 --- a/src/test/java/org/folio/search/utils/CallNumberUtilsTest.java +++ b/src/test/java/org/folio/search/utils/CallNumberUtilsTest.java @@ -1,29 +1,12 @@ package org.folio.search.utils; -import static com.google.common.collect.Lists.newArrayList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; import static org.apache.commons.lang3.StringUtils.compareIgnoreCase; import static org.assertj.core.api.Assertions.assertThat; -import static org.folio.search.model.types.CallNumberType.DEWEY; -import static org.folio.search.model.types.CallNumberType.LC; -import static org.folio.search.model.types.CallNumberType.NLM; -import static org.folio.search.utils.TestConstants.TENANT_ID; -import static org.folio.search.utils.TestUtils.getShelfKeyFromCallNumber; -import static org.folio.search.utils.TestUtils.randomId; import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.opensearch.index.query.QueryBuilders.boolQuery; -import java.util.List; -import java.util.UUID; import java.util.stream.IntStream; import java.util.stream.Stream; import one.util.streamex.StreamEx; -import org.folio.search.domain.dto.CallNumberBrowseItem; -import org.folio.search.domain.dto.Instance; -import org.folio.search.domain.dto.Item; -import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; -import org.folio.search.model.service.BrowseContext; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -32,7 +15,6 @@ import org.junit.jupiter.params.provider.CsvFileSource; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; -import org.opensearch.index.query.TermQueryBuilder; @UnitTest class CallNumberUtilsTest { @@ -122,78 +104,6 @@ void getNormalizedCallNumber_with_suffix_prefix_positive() { assertThat(actual).isEqualTo("prefix94nf14137923835suffix"); } - @ParameterizedTest(name = "[{index}] callNumber={0}, records={1}, expected={2}") - @MethodSource("eliminateIrrelevantItemsOnCallNumberBrowsingData") - void excludeIrrelevantResultItems(String callNumberType, List given, - List expected) { - var context = BrowseContext.builder().build(); - var items = CallNumberUtils.excludeIrrelevantResultItems(context, callNumberType, emptySet(), given); - assertThat(items).isEqualTo(expected); - var unchangedItems = CallNumberUtils.excludeIrrelevantResultItems(context, "", emptySet(), given); - assertThat(unchangedItems).isEqualTo(given); - } - - @Test - public void excludeIrrelevantResultItems_with_null_iecnc() { - var context = BrowseContext.builder().build(); - var givenItems = createItemWithNullEffectiveCallNumberComponents(); - var callNumberTypeValue = "dewey"; - - var resultItems = CallNumberUtils - .excludeIrrelevantResultItems(context, callNumberTypeValue, emptySet(), givenItems); - - assertThat(resultItems).isEmpty(); - } - - @Test - void excludeIrrelevantResultItems_positive_tenantFilter() { - var tenantId = "tenant"; - var context = BrowseContext.builder() - .filters(List.of(new TermQueryBuilder("holdings.tenantId", tenantId))) - .build(); - var data = List.>of(newArrayList(null, "cn", "00000000-0000-0000-0000-000000000006")); - var browseItems = List.of(browseItem(data, "id", "cn", TENANT_ID), - browseItem(data, "id", "cn", tenantId)); - var expected = List.of(browseItems.get(1)); - - var items = CallNumberUtils.excludeIrrelevantResultItems(context, null, emptySet(), browseItems); - assertThat(items).isEqualTo(expected); - } - - @Test - void excludeIrrelevantResultItems_positive_locationFilter() { - var effectiveLocationId = UUID.randomUUID().toString(); - var context = BrowseContext.builder() - .filters(List.of(new TermQueryBuilder("items.effectiveLocationId", effectiveLocationId))) - .build(); - var data = List.>of(newArrayList(null, "cn", "00000000-0000-0000-0000-000000000006")); - var browseItems = List.of(browseItem(data, "id", "cn", TENANT_ID), - browseItem(data, "id", "cn", TENANT_ID, effectiveLocationId)); - var expected = List.of(browseItems.get(1)); - - var items = CallNumberUtils.excludeIrrelevantResultItems(context, null, emptySet(), browseItems); - assertThat(items).isEqualTo(expected); - } - - @Test - void excludeIrrelevantResultItems_positive_multipleLocationFilter() { - var effectiveLocationId1 = UUID.randomUUID().toString(); - var effectiveLocationId2 = UUID.randomUUID().toString(); - var context = BrowseContext.builder() - .filters(List.of(boolQuery() - .should(new TermQueryBuilder("items.effectiveLocationId", effectiveLocationId1)) - .should(new TermQueryBuilder("items.effectiveLocationId", effectiveLocationId2)))) - .build(); - var data = List.>of(newArrayList(null, "cn", "00000000-0000-0000-0000-000000000006")); - var browseItems = List.of(browseItem(data, "id", "cn", TENANT_ID), - browseItem(data, "id", "cn", TENANT_ID, effectiveLocationId1), - browseItem(data, "id", "cn", TENANT_ID, effectiveLocationId2)); - var expected = browseItems.subList(1, 3); - - var items = CallNumberUtils.excludeIrrelevantResultItems(context, null, emptySet(), browseItems); - assertThat(items).isEqualTo(expected); - } - private static Stream supportedCharactersDataset() { return StreamEx.empty() .append(letterCharacterDataProvider()) @@ -213,120 +123,4 @@ private static Stream otherCharactersDataProvider() { return ".,:;=-+~_/\\#@?!".chars().mapToObj(e -> arguments((char) e)); } - private static CallNumberBrowseItem browseItem(List> data, String instanceId, String fullCallNumber) { - return browseItem(data, instanceId, fullCallNumber, TENANT_ID); - } - - private static CallNumberBrowseItem browseItem(List> data, String instanceId, String fullCallNumber, - String tenantId) { - return browseItem(data, instanceId, fullCallNumber, tenantId, null); - } - - private static CallNumberBrowseItem browseItem(List> data, String instanceId, String fullCallNumber, - String tenantId, String effectiveLocationId) { - var items = data.stream().map(d -> new Item() - .id(d.get(2)) - .tenantId(tenantId) - .effectiveLocationId(effectiveLocationId) - .discoverySuppress(false) - .effectiveCallNumberComponents(new ItemEffectiveCallNumberComponents() - .callNumber(d.get(1)) - .suffix(d.size() > 3 ? d.get(3) : null) - .typeId(d.get(0))) - .effectiveShelvingOrder(getShelfKeyFromCallNumber(d.get(1)))) - .toList(); - - var instance = new Instance() - .id(instanceId) - .title("instance #01") - .staffSuppress(false) - .discoverySuppress(false) - .isBoundWith(false) - .shared(false) - .tenantId(TENANT_ID) - .items(items) - .holdings(emptyList()); - return new CallNumberBrowseItem() - .fullCallNumber(fullCallNumber) - .instance(instance); - } - - public static List createItemWithNullEffectiveCallNumberComponents() { - var testId = randomId(); - var data = List.of(newArrayList(DEWEY.getId(), "308 H977", "00000000-0000-0000-0000-000000000001")); - - var items = data.stream().map(d -> new Item() - .id(d.get(2)) - .tenantId("tenant") - .effectiveLocationId(testId) - .discoverySuppress(false) - .effectiveCallNumberComponents(null) - .effectiveShelvingOrder(getShelfKeyFromCallNumber(d.get(1)))) - .toList(); - - var instance = new Instance() - .id(testId) - .title("instance #01") - .staffSuppress(false) - .discoverySuppress(false) - .isBoundWith(false) - .shared(false) - .tenantId(TENANT_ID) - .items(items) - .holdings(emptyList()); - - var callNumberBrowseItem = new CallNumberBrowseItem() - .fullCallNumber("cn") - .instance(instance); - - return List.of(callNumberBrowseItem); - } - - private static Stream eliminateIrrelevantItemsOnCallNumberBrowsingData() { - var id = randomId(); - var mixedData = List.of( - List.of(LC.getId(), "Z669.R360 197", "00000000-0000-0000-0000-000000000000"), - List.of(DEWEY.getId(), "308 H977", "00000000-0000-0000-0000-000000000001") - ); - var deweyData = List.of( - List.of(DEWEY.getId(), "308 H977", "00000000-0000-0000-0000-000000000001") - ); - - var mixedLcData = List.of( - List.of(LC.getId(), "Z669.R360 197", "00000000-0000-0000-0000-000000000000"), - List.of(DEWEY.getId(), "308 H977", "00000000-0000-0000-0000-000000000001"), - List.of(NLM.getId(), "WE 200-600", "00000000-0000-0000-0000-000000000002"), - List.of(NLM.getId(), "WE 200-700", "00000000-0000-0000-0000-000000000003") - ); - - var lcData = List.of( - List.of(NLM.getId(), "WE 200-600", "00000000-0000-0000-0000-000000000002"), - List.of(NLM.getId(), "WE 200-700", "00000000-0000-0000-0000-000000000003") - ); - - var nlmSuffixData = List.of( - List.of(NLM.getId(), "QS 11 .GA1 E53", "00000000-0000-0000-0000-000000000004", "2005") - ); - - var localData = List.of( - List.of(UUID.randomUUID().toString(), "localCn1", "00000000-0000-0000-0000-000000000004") - ); - var localAndNotTypedData = List.of( - localData.get(0), - newArrayList(null, "noTypedCn", "00000000-0000-0000-0000-000000000006") - ); - - - return Stream.of( - arguments("dewey", List.of(browseItem(mixedData, id, "308 H977")), - List.of(browseItem(deweyData, id, "308 H977"))), - arguments("nlm", List.of(browseItem(mixedLcData, id, "WE 200-600")), - List.of(browseItem(lcData, id, "WE 200-600"))), - arguments("nlm", List.of(browseItem(nlmSuffixData, id, "QS 11 .GA1 E53 2005")), - List.of(browseItem(nlmSuffixData, id, "QS 11 .GA1 E53 2005"))), - arguments("local", List.of(browseItem(localAndNotTypedData, id, "localCn1")), - List.of(browseItem(localData, id, "localCn1"))) - ); - } - } diff --git a/src/test/java/org/folio/search/utils/TestUtils.java b/src/test/java/org/folio/search/utils/TestUtils.java index 79a3ca6d7..d92c86c4c 100644 --- a/src/test/java/org/folio/search/utils/TestUtils.java +++ b/src/test/java/org/folio/search/utils/TestUtils.java @@ -78,6 +78,8 @@ import org.folio.search.domain.dto.ItemEffectiveCallNumberComponents; import org.folio.search.domain.dto.LanguageConfig; import org.folio.search.domain.dto.LanguageConfigs; +import org.folio.search.domain.dto.LegacyCallNumberBrowseItem; +import org.folio.search.domain.dto.LegacyCallNumberBrowseResult; import org.folio.search.domain.dto.ResourceEvent; import org.folio.search.domain.dto.ResourceEventType; import org.folio.search.domain.dto.SubjectBrowseItem; @@ -198,25 +200,26 @@ public static CallNumberBrowseResult cnBrowseResult(int total, List Optional.ofNullable(item.getEffectiveCallNumberComponents()) .map(ItemEffectiveCallNumberComponents::getTypeId)); @@ -229,36 +232,37 @@ public static CallNumberBrowseItem cnBrowseItem(Instance instance, String callNu .isBoundWith(instance.getIsBoundWith()) .items(null) .holdings(null); - return new CallNumberBrowseItem().fullCallNumber(callNumber).shelfKey(shelfKey).instance(ins).totalRecords(1) + return new LegacyCallNumberBrowseItem().fullCallNumber(callNumber).shelfKey(shelfKey).instance(ins).totalRecords(1) .isAnchor(isAnchor); } - public static CallNumberBrowseItem cnBrowseItem(CallNumberType callNumberType, String callNumber, - Integer totalRecords, Boolean isAnchor) { + public static LegacyCallNumberBrowseItem cnBrowseItem(CallNumberType callNumberType, String callNumber, + Integer totalRecords, Boolean isAnchor) { var shelfKey = getShelfKeyFromCallNumber(callNumber, callNumberType.getId()); - return new CallNumberBrowseItem().fullCallNumber(callNumber).shelfKey(shelfKey).instance(null) + return new LegacyCallNumberBrowseItem().fullCallNumber(callNumber).shelfKey(shelfKey).instance(null) .totalRecords(totalRecords).isAnchor(isAnchor); } - public static CallNumberBrowseItem cnBrowseItem(int totalRecords, String callNumber) { + public static LegacyCallNumberBrowseItem cnBrowseItem(int totalRecords, String callNumber) { var shelfKey = getShelfKeyFromCallNumber(callNumber); - return new CallNumberBrowseItem().totalRecords(totalRecords).shelfKey(shelfKey).fullCallNumber(callNumber); + return new LegacyCallNumberBrowseItem().totalRecords(totalRecords).shelfKey(shelfKey).fullCallNumber(callNumber); } - public static CallNumberBrowseItem cnBrowseItem(int totalRecords, String callNumber, boolean isAnchor) { + public static LegacyCallNumberBrowseItem cnBrowseItem(int totalRecords, String callNumber, boolean isAnchor) { var shelfKey = getShelfKeyFromCallNumber(callNumber); - return new CallNumberBrowseItem().totalRecords(totalRecords).shelfKey(shelfKey).fullCallNumber(callNumber) + return new LegacyCallNumberBrowseItem().totalRecords(totalRecords).shelfKey(shelfKey).fullCallNumber(callNumber) .isAnchor(isAnchor); } - public static CallNumberBrowseItem cnBrowseItemWithNoType(Instance instance, String callNumber) { + public static LegacyCallNumberBrowseItem cnBrowseItemWithNoType(Instance instance, String callNumber) { return cnBrowseItemWithNoType(instance, callNumber, null); } - public static CallNumberBrowseItem cnBrowseItemWithNoType(Instance instance, String callNumber, Boolean isAnchor) { + public static LegacyCallNumberBrowseItem cnBrowseItemWithNoType(Instance instance, String callNumber, + Boolean isAnchor) { var shelfKey = getShelfKeyFromCallNumber(callNumber); - return new CallNumberBrowseItem().fullCallNumber(callNumber).shelfKey(shelfKey).instance(instance).totalRecords(1) - .isAnchor(isAnchor); + return new LegacyCallNumberBrowseItem().fullCallNumber(callNumber) + .shelfKey(shelfKey).instance(instance).totalRecords(1).isAnchor(isAnchor); } public static String getShelfKeyFromCallNumber(String callNumber) { @@ -372,26 +376,18 @@ public static SearchDocumentBody searchDocumentBodyToDelete() { return SearchDocumentBody.of(null, null, resourceEvent(), DELETE); } - public static void cleanupActual(CallNumberBrowseResult actual) { + public static void cleanupActual(LegacyCallNumberBrowseResult actual) { for (var item : actual.getItems()) { cleanupCallNumberItem(item); } } - public static void cleanupActual(BrowseResult actual) { + public static void cleanupActual(BrowseResult actual) { for (var item : actual.getRecords()) { cleanupCallNumberItem(item); } } - private static void cleanupCallNumberItem(CallNumberBrowseItem item) { - var instance = item.getInstance(); - if (instance != null) { - instance.setItems(null); - instance.setHoldings(null); - } - } - @SuppressWarnings("unchecked") public static Map mapOf(K k1, V v1, Object... pairs) { Map map = new LinkedHashMap<>(); @@ -703,6 +699,14 @@ public static MappingBuilder mockCallNumberTypes(WireMockServer wireMockServer, return stub; } + private static void cleanupCallNumberItem(LegacyCallNumberBrowseItem item) { + var instance = item.getInstance(); + if (instance != null) { + instance.setItems(null); + instance.setHoldings(null); + } + } + private static JsonNode searchResponseWithAggregation(JsonNode aggregationValue) { return jsonObject("took", 0, "timed_out", false, "_shards", jsonObject("total", 1, "successful", 1, "skipped", 0, "failed", 0), "hits", diff --git a/src/test/resources/samples/cn-browse/call-numbers.csv b/src/test/resources/samples/cn-browse/call-numbers.csv new file mode 100644 index 000000000..369b0eeb4 --- /dev/null +++ b/src/test/resources/samples/cn-browse/call-numbers.csv @@ -0,0 +1,101 @@ +Num,Call Number,Type,Prefix,Suffix,Volume,Enumeration,Chronology,Copy Number,Location Num +1,TA357 .A78 2010,LC,,,v.1,,,c.1,1 +2,664.051 4,DEWEY,,,,v.2 no.1-3 1949,,c.1,2 +3,WG 200 M489 1969,NLM,,,,,,,1 +4,Y 10.13:980,SUDOC,,,,,,,1 +5,PICOULT,OTHER,FIC,,,,,,1 +6,HB30.G64 1974,LC,microfm,,,,,,1 +7,618.92/12,DEWEY,,,,,,,1 +8,QZ 380 N494 1979,NLM,,,,,,,2 +9,Y 4.En 2/3:99-96,SUDOC,,,,,,,2 +10,FICTION HART,OTHER,LARGE PRINT,,,,,,3 +11,Z997.S93,LC,,,,,,,1 +12,748.5,DEWEY,DVD,STA,,,,,1 +13,WG140 H719b,NLM,,,,,,,2 +14,HE 20.315/2: 96-4,SUDOC,US MicFiche,,,,,,3 +15,BLUE,OTHER,POP/ROCK,CD,,,,,1 +16,RJ421 .D3,LC,,,,,,Copy 1,1 +17,818/.309 19,DEWEY,,,,,,,2 +18,WL 500 I61p,NLM,,,,,,c.1,3 +19,Y 4.En2/3:99th 99-96,SUDOC,US,,,,,,1 +20,CHOPIN,OTHER,CD PIANO,,,,,,2 +21,QP363 .N6 1965,LC,,FT MEADE,,,,Copy 2,3 +22,929.1973,DEWEY,GEN,MCH,,,,,1 +23,WK 385 P973e 1969,NLM,,,,,,,2 +24,GS1.2:So2/4,SUDOC,,,,,,,3 +25,SYLY-12,OTHER,,,,,,,1 +26,RC280.N4 N49,LC,,,,,,,2 +27,912.773,DEWEY,LH ,,,,,,1 +28,WS105 O52m 1929f,NLM,,,,,,,3 +29,J29.2:D84/2,SUDOC,,,,,,,1 +30,M653-202,OTHER,MICROFILM,,,,,,2 +31,GV837 .F92,LC,,,,,,c.1,2 +32,977.322,DEWEY,LH DVD,MCH,,,,,1 +33,WB 100 M4891,NLM,,,,,,,2 +34,EP 1.8: Ac 7/2,SUDOC,,,,,,,3 +35,Rowling,OTHER,jFICTION,,,,,,1 +36,AN2.I45 C48,LC,,,,,,,2 +37,616.99/383,DEWEY,,,,,,,3 +38,QX 4 J647m,NLM,,,,,,,1 +39,Y 4.C 73/8:115-85,SUDOC,USG,,,,,,2 +40,0634V,OTHER,,,,,,,2 +41,RC667 .N47 2010,LC,,,,,,c. 1,2 +42,912.773,DEWEY,R,(LOCAL HISTORY),,,,,2 +43,WC 450 R47 2012,NLM,,,,,,,2 +44,Y4.J89/2:R53,SUDOC,,,,,,c.1,3 +45,DKLY-12,OTHER,,,,,,,3 +46,PJ4.J8,LC,,,,v.227 no.2 1935,,c.2,3 +47,929.31,DEWEY,REF,,,,,,3 +48,WL 11 AA1 J64 2016,NLM,,,,,,,1 +49,Y10.2:P38,SUDOC,microfc,,,,,,1 +50,0.257638889,OTHER,Oversize,,,,,,1 +51,QR1.I6,LC,,,v.12,1898,,c.1,1 +52,370.977,DEWEY,,DEK,,,,,2 +53,WS 420 F1985 2015,NLM,,,,,,,1 +54,I 29.6/6:F 75/988,SUDOC,,,,,,,1 +55,977,OTHER,,1987 ED.,,,,,1 +56,EMI 458,LC,AudCD,,,,,,1 +57,338.1,DEWEY,R,MOG,,,,,1 +58,WY 11 AA1 J92h 2010,NLM,,,,,,,2 +59,A 1.68: 1461/991,SUDOC,,,,,,c.4,2 +60,Fiction Rowling,OTHER,Teen Spanish,,,,,,3 +61,D848 .A1934 2018,LC,,,,,,,1 +62,929.377328,DEWEY,R,GSDC,,,,,1 +63,WK 850 D5363 2010,NLM,,,,,,,2 +64,J 1.8/2:R 11/988,SUDOC,,,,,,,3 +65,Meyer ,OTHER,Teen Graphic Novel,,v.1,,,,1 +66,HV6021 .W55 2018,LC,,,,,,,1 +67,929.5,DEWEY,LOCAL ,CEM,,,,,2 +68,WA 900.AA1 I55h 1996,NLM,,,,,,,3 +69,A 1.28:823,SUDOC,U S Doc,,,,,,1 +70,Twilight Breaking,OTHER,DVD,pt. 1,,2 discs,,,2 +71,SB1.P71,LC,,,,no.146-147 1996,,,3 +72,977.328,DEWEY,,BIG,,,,,1 +73,QV 18.2 L765 2015,NLM,Stacks,,,,,c.3,2 +74,I 1.98:M 77,SUDOC,,Final,,,,,3 +75,King,OTHER,CD Horror,,,13 discs,,,1 +76,BJ1499.S65,LC,,,,,,,2 +77,371.782,DEWEY,SOCIAL SCIENCE,ARC,,,,,1 +78,Wv 4 B311 2018,NLM,Reserve,,,,,c.2,3 +79,Y 4.Ag 8/3:H 91,SUDOC,,,,,,,1 +80,Collins,OTHER,Picture Book,,,,,,2 +81,E99.I7 P4,LC,,,,,,,2 +82,363.41,DEWEY,YA,WAT,,,,,1 +83,WV 11.1 L435h 1975,NLM,History,,,,,,2 +84,Y 1.1/8:98-588/pt.1,SUDOC,,,,,,,3 +85,MMC-3625,OTHER,,,,,,,1 +86,Q127.U6U49,LC,,,,1981 source materials v.1,,c.1,2 +87,553.7,DEWEY,J,STE,,,,,3 +88,WF 11.1 J13a 2009,NLM,,,,,,,1 +89,A 1.38:290/1945,SUDOC,,,,,,,2 +90,9200 4683,OTHER,f,,,"no.51-96,98-140 1972-75",,,2 +91,SDD 66727,LC,,,,,,,2 +92,791.4502,DEWEY,CD,,,5 discs,,,2 +93,WD 308 1993,NLM,,,,,,,2 +94,Y 3.T 22/2:2 F 76/Sum,SUDOC,,,,,,,3 +95,0517G,OTHER,,,,,,,3 +96,JZ1720 .C75 2019,LC,,,,,,,3 +97,015/.73,DEWEY,,,,,,,3 +98,WY 153.5 P467,NLM,,,,,,,1 +99,Y 3.T 22/2:2 St 8/2,SUDOC,,,,,,,1 +100,"15,747-12N-12P",OTHER,Microfilm ,,,(Series 1-3),,,1 diff --git a/src/test/resources/samples/cn-browse/instances-with-call-numbers.csv b/src/test/resources/samples/cn-browse/instances-with-call-numbers.csv new file mode 100644 index 000000000..66a9d5491 --- /dev/null +++ b/src/test/resources/samples/cn-browse/instances-with-call-numbers.csv @@ -0,0 +1,51 @@ +Num,Call Number Nums +1,1;2 +2,1 +3,3;4 +4,5;6;7;8 +5,1;8;9 +6,10;11 +7,12 +8,8;13;14 +9,15 +10,16 +11,17 +12,18;19;20 +13,21 +14,22 +15,23 +16,24;25;26;27;28;29;30 +17,22;29;31;32 +18,33;34 +19,35;36 +20,37;38;39;40 +21,21;22;23 +22,41 +23,42 +24,43 +25,44 +26,45;46 +27,47;48;49;50 +28,51;52;53 +29,54;55;56 +30,57;58;59;60 +31,61;62;63;64 +32,65 +33,66 +34,67 +35,68;69;70 +36,71;72;73;74;75 +37,76 +38,77 +39,78 +40,79;80 +41,81;82 +42,83;84 +43,85;86 +44,87;88 +45,89;90 +46,91;92 +47,93;94 +48,95;96 +49,97;98 +50,99;100 \ No newline at end of file diff --git a/src/test/resources/samples/cn-browse/locations.csv b/src/test/resources/samples/cn-browse/locations.csv new file mode 100644 index 000000000..52d1d4cad --- /dev/null +++ b/src/test/resources/samples/cn-browse/locations.csv @@ -0,0 +1,4 @@ +Num,Location +1,65b6c2e9-8a7b-4a10-9b5d-ba1cf0313cd7 +2,b777f3a4-4372-4792-a87d-8e8f177eab10 +3,0d106980-1789-42ac-b355-a6c7a74ddea3 \ No newline at end of file