diff --git a/NEWS.md b/NEWS.md index a8886a20c..b226b16ab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,7 @@ * Facets: add support for instance classification facets ([MSEARCH-606](https://issues.folio.org/browse/MSEARCH-606)) * Return Unified List of Inventory Locations in a Consortium ([MSEARCH-681](https://folio-org.atlassian.net/browse/MSEARCH-681)) * Remove ability to match on LCCN searches without a prefix ([MSEARCH-752](https://folio-org.atlassian.net/browse/MSEARCH-752)) +* Search consolidated items/holdings data in consortium ([MSEARCH-759](https://folio-org.atlassian.net/browse/MSEARCH-759)) ### Bug fixes * Do not delete kafka topics if collection topic is enabled ([MSEARCH-725](https://folio-org.atlassian.net/browse/MSEARCH-725)) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 5087b9c57..b2b5e29c8 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -179,6 +179,54 @@ "modulePermissions": [ "user-tenants.collection.get" ] + }, + { + "methods": [ + "GET" + ], + "pathPattern": "/search/consortium/holding/{id}", + "permissionsRequired": [ + "consortium-search.holdings.item.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] + }, + { + "methods": [ + "GET" + ], + "pathPattern": "/search/consortium/item/{id}", + "permissionsRequired": [ + "consortium-search.items.item.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] + }, + { + "methods": [ + "POST" + ], + "pathPattern": "/search/consortium/batch/items", + "permissionsRequired": [ + "consortium-search.items.collection.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] + }, + { + "methods": [ + "POST" + ], + "pathPattern": "/search/consortium/batch/holdings", + "permissionsRequired": [ + "consortium-search.holdings.collection.get" + ], + "modulePermissions": [ + "user-tenants.collection.get" + ] } ] }, @@ -648,6 +696,16 @@ "displayName": "Consortium Search - fetch items records", "description": "Returns items records in consortium" }, + { + "permissionName": "consortium-search.holdings.item.get", + "displayName": "Consortium Search - fetch holdings record", + "description": "Returns holdings record in consortium" + }, + { + "permissionName": "consortium-search.items.item.get", + "displayName": "Consortium Search - fetch item record", + "description": "Returns item record in consortium" + }, { "permissionName": "consortium-search.locations.collection.get", "displayName": "Consortium Search - fetch locations records", diff --git a/src/main/java/org/folio/search/controller/SearchConsortiumController.java b/src/main/java/org/folio/search/controller/SearchConsortiumController.java index 7d0fc85ba..3e0f1f1b7 100644 --- a/src/main/java/org/folio/search/controller/SearchConsortiumController.java +++ b/src/main/java/org/folio/search/controller/SearchConsortiumController.java @@ -1,14 +1,27 @@ package org.folio.search.controller; +import static org.folio.search.utils.SearchUtils.INSTANCE_HOLDING_FIELD_NAME; +import static org.folio.search.utils.SearchUtils.INSTANCE_ITEM_FIELD_NAME; + +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.search.domain.dto.BatchIdsDto; +import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.ConsortiumHoldingCollection; +import org.folio.search.domain.dto.ConsortiumItem; import org.folio.search.domain.dto.ConsortiumItemCollection; import org.folio.search.domain.dto.ConsortiumLocationCollection; +import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.SortOrder; import org.folio.search.exception.RequestValidationException; import org.folio.search.model.service.ConsortiumSearchContext; +import org.folio.search.model.service.CqlSearchRequest; import org.folio.search.model.types.ResourceType; import org.folio.search.rest.resource.SearchConsortiumApi; +import org.folio.search.service.consortium.ConsortiumInstanceSearchService; import org.folio.search.service.consortium.ConsortiumInstanceService; import org.folio.search.service.consortium.ConsortiumLocationService; import org.folio.search.service.consortium.ConsortiumTenantService; @@ -18,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Log4j2 @Validated @RestController @RequiredArgsConstructor @@ -30,13 +44,14 @@ public class SearchConsortiumController implements SearchConsortiumApi { private final ConsortiumTenantService consortiumTenantService; private final ConsortiumInstanceService instanceService; private final ConsortiumLocationService locationService; + private final ConsortiumInstanceSearchService searchService; @Override public ResponseEntity getConsortiumHoldings(String tenantHeader, String instanceId, String tenantId, Integer limit, Integer offset, String sortBy, SortOrder sortOrder) { - checkAllowance(tenantHeader); + verifyAndGetTenant(tenantHeader); var context = ConsortiumSearchContext.builderFor(ResourceType.HOLDINGS) .filter("instanceId", instanceId) .filter("tenantId", tenantId) @@ -53,7 +68,7 @@ public ResponseEntity getConsortiumItems(String tenant String holdingsRecordId, String tenantId, Integer limit, Integer offset, String sortBy, SortOrder sortOrder) { - checkAllowance(tenantHeader); + verifyAndGetTenant(tenantHeader); var context = ConsortiumSearchContext.builderFor(ResourceType.ITEM) .filter("instanceId", instanceId) .filter("tenantId", tenantId) @@ -73,7 +88,7 @@ public ResponseEntity getConsortiumLocations(Strin Integer offset, String sortBy, SortOrder sortOrder) { - checkAllowance(tenantHeader); + verifyAndGetTenant(tenantHeader); var result = locationService.fetchLocations(tenantHeader, tenantId, limit, offset, sortBy, sortOrder); return ResponseEntity.ok(new @@ -82,11 +97,72 @@ public ResponseEntity getConsortiumLocations(Strin .totalRecords(result.getTotalRecords())); } - private void checkAllowance(String tenantHeader) { + @Override + public ResponseEntity getConsortiumHolding(UUID id, String tenantHeader) { + var tenant = verifyAndGetTenant(tenantHeader); + var holdingId = id.toString(); + var searchRequest = idCqlRequest(tenant, INSTANCE_HOLDING_FIELD_NAME, holdingId); + + var result = searchService.getConsortiumHolding(holdingId, searchRequest); + return ResponseEntity.ok(result); + } + + @Override + public ResponseEntity getConsortiumItem(UUID id, String tenantHeader) { + var tenant = verifyAndGetTenant(tenantHeader); + var itemId = id.toString(); + var searchRequest = idCqlRequest(tenant, INSTANCE_ITEM_FIELD_NAME, itemId); + + var result = searchService.getConsortiumItem(itemId, searchRequest); + return ResponseEntity.ok(result); + } + + @Override + public ResponseEntity fetchConsortiumBatchHoldings(String tenantHeader, + BatchIdsDto batchIdsDto) { + var tenant = verifyAndGetTenant(tenantHeader); + var holdingIds = batchIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); + var searchRequest = idsCqlRequest(tenant, INSTANCE_HOLDING_FIELD_NAME, holdingIds); + + var result = searchService.fetchConsortiumBatchHoldings(searchRequest, holdingIds); + return ResponseEntity.ok(result); + } + + @Override + public ResponseEntity fetchConsortiumBatchItems(String tenantHeader, + BatchIdsDto batchIdsDto) { + if (batchIdsDto.getIds().isEmpty()) { + return ResponseEntity + .ok(new ConsortiumItemCollection()); + } + + var tenant = verifyAndGetTenant(tenantHeader); + var itemIds = batchIdsDto.getIds().stream().map(UUID::toString).collect(Collectors.toSet()); + var searchRequest = idsCqlRequest(tenant, INSTANCE_ITEM_FIELD_NAME, itemIds); + + var result = searchService.fetchConsortiumBatchItems(searchRequest, itemIds); + return ResponseEntity.ok(result); + } + + private String verifyAndGetTenant(String tenantHeader) { var centralTenant = consortiumTenantService.getCentralTenant(tenantHeader); if (centralTenant.isEmpty() || !centralTenant.get().equals(tenantHeader)) { throw new RequestValidationException(REQUEST_NOT_ALLOWED_MSG, XOkapiHeaders.TENANT, tenantHeader); } + return centralTenant.get(); + } + + private CqlSearchRequest idCqlRequest(String tenant, String fieldName, String id) { + var query = fieldName + ".id=" + id; + return CqlSearchRequest.of(Instance.class, tenant, query, 1, 0, true, false, true); + } + + private CqlSearchRequest idsCqlRequest(String tenant, String fieldName, Set ids) { + var query = ids.stream() + .map((fieldName + ".id=%s")::formatted) + .collect(Collectors.joining(" or ")); + + return CqlSearchRequest.of(Instance.class, tenant, query, ids.size(), 0, true, false, true); } } diff --git a/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java b/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java new file mode 100644 index 000000000..d92c894dd --- /dev/null +++ b/src/main/java/org/folio/search/converter/ConsortiumHoldingMapper.java @@ -0,0 +1,24 @@ +package org.folio.search.converter; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.folio.search.domain.dto.ConsortiumHolding; +import org.folio.search.domain.dto.Holding; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ConsortiumHoldingMapper { + + public static ConsortiumHolding toConsortiumHolding(String instanceId, Holding holding) { + return new ConsortiumHolding() + .id(holding.getId()) + .hrid(holding.getHrid()) + .tenantId(holding.getTenantId()) + .instanceId(instanceId) + .callNumberPrefix(holding.getCallNumberPrefix()) + .callNumber(holding.getCallNumber()) + .callNumberSuffix(holding.getCallNumberSuffix()) + .copyNumber(holding.getCopyNumber()) + .permanentLocationId(holding.getPermanentLocationId()) + .discoverySuppress(holding.getDiscoverySuppress() != null && holding.getDiscoverySuppress()); + } +} diff --git a/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java b/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java new file mode 100644 index 000000000..830fd1236 --- /dev/null +++ b/src/main/java/org/folio/search/converter/ConsortiumItemMapper.java @@ -0,0 +1,20 @@ +package org.folio.search.converter; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.folio.search.domain.dto.ConsortiumItem; +import org.folio.search.domain.dto.Item; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ConsortiumItemMapper { + + public static ConsortiumItem toConsortiumItem(String instanceId, Item item) { + return new ConsortiumItem() + .id(item.getId()) + .hrid(item.getHrid()) + .tenantId(item.getTenantId()) + .instanceId(instanceId) + .holdingsRecordId(item.getHoldingsRecordId()) + .barcode(item.getBarcode()); + } +} diff --git a/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java b/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java index cf573564f..13f90c9e6 100644 --- a/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java +++ b/src/main/java/org/folio/search/cql/CqlSearchQueryConverter.java @@ -68,9 +68,16 @@ public SearchSourceBuilder convert(String query, String resource) { * @return search source as {@link SearchSourceBuilder} object with query and sorting conditions */ public SearchSourceBuilder convertForConsortia(String query, String resource) { + return convertForConsortia(query, resource, false); + } + + public SearchSourceBuilder convertForConsortia(String query, String resource, boolean consortiumConsolidated) { var sourceBuilder = convert(query, resource); - var queryBuilder = consortiumSearchHelper.filterQueryForActiveAffiliation(sourceBuilder.query(), resource); + if (consortiumConsolidated) { + return sourceBuilder; + } + var queryBuilder = consortiumSearchHelper.filterQueryForActiveAffiliation(sourceBuilder.query(), resource); return sourceBuilder.query(queryBuilder); } diff --git a/src/main/java/org/folio/search/model/service/CqlSearchRequest.java b/src/main/java/org/folio/search/model/service/CqlSearchRequest.java index b89258e0d..a5856e87d 100644 --- a/src/main/java/org/folio/search/model/service/CqlSearchRequest.java +++ b/src/main/java/org/folio/search/model/service/CqlSearchRequest.java @@ -53,6 +53,11 @@ public class CqlSearchRequest implements ResourceRequest { */ private final Boolean includeNumberOfTitles; + /** + * Doesn't affect non-consortium. true means include all records, false means filter for active affiliation. + */ + private final Boolean consortiumConsolidated; + /** * Creates {@link CqlSearchRequest} object for given variables. * @@ -64,14 +69,22 @@ public class CqlSearchRequest implements ResourceRequest { * @param expandAll - whether to return only response properties or entire record * @param - generic type for {@link CqlSearchRequest} object. * @param includeNumberOfTitles - indicates whether the number of titles should be counted. + * @param consortiumConsolidated - indicates whether to return consortium consolidated records. * @return created {@link CqlSearchRequest} object */ public static CqlSearchRequest of(Class resourceClass, String tenantId, String query, Integer limit, Integer offset, Boolean expandAll, - Boolean includeNumberOfTitles) { + Boolean includeNumberOfTitles, Boolean consortiumConsolidated) { var resource = SearchUtils.getResourceName(resourceClass); return new CqlSearchRequest<>(resource, resourceClass, tenantId, query, limit, offset, expandAll, - includeNumberOfTitles); + includeNumberOfTitles, consortiumConsolidated); + } + + public static CqlSearchRequest of(Class resourceClass, String tenantId, String query, + Integer limit, Integer offset, Boolean expandAll, + Boolean includeNumberOfTitles) { + return CqlSearchRequest.of(resourceClass, tenantId, query, limit, offset, expandAll, + includeNumberOfTitles, false); } public static CqlSearchRequest of(Class resourceClass, String tenantId, String query, diff --git a/src/main/java/org/folio/search/service/SearchService.java b/src/main/java/org/folio/search/service/SearchService.java index 5bf28e1a7..962c258b0 100644 --- a/src/main/java/org/folio/search/service/SearchService.java +++ b/src/main/java/org/folio/search/service/SearchService.java @@ -3,6 +3,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.commons.lang3.BooleanUtils.isFalse; import static org.folio.search.model.types.ResponseGroupType.SEARCH; +import static org.folio.search.utils.SearchUtils.buildPreferenceKey; import java.util.List; import java.util.Map; @@ -54,7 +55,8 @@ public SearchResult search(CqlSearchRequest request) { } var resource = request.getResource(); var requestTimeout = searchQueryConfiguration.getRequestTimeout(); - var queryBuilder = cqlSearchQueryConverter.convertForConsortia(request.getQuery(), resource) + var queryBuilder = cqlSearchQueryConverter.convertForConsortia(request.getQuery(), resource, + request.getConsortiumConsolidated()) .from(request.getOffset()) .size(request.getLimit()) .trackTotalHits(true) @@ -76,10 +78,6 @@ public SearchResult search(CqlSearchRequest request) { return searchResult; } - private String buildPreferenceKey(String tenantId, String resource, String query) { - return tenantId + "-" + resource + "-" + query; - } - private void searchResultPostProcessing(Class resourceClass, boolean includeNumberOfTitles, SearchResult searchResult) { if (Objects.isNull(resourceClass)) { diff --git a/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java b/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java new file mode 100644 index 000000000..c76ddf2b0 --- /dev/null +++ b/src/main/java/org/folio/search/service/consortium/ConsortiumInstanceSearchService.java @@ -0,0 +1,100 @@ +package org.folio.search.service.consortium; + +import static org.apache.commons.collections4.CollectionUtils.isEmpty; + +import java.util.Objects; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.search.converter.ConsortiumHoldingMapper; +import org.folio.search.converter.ConsortiumItemMapper; +import org.folio.search.domain.dto.ConsortiumHolding; +import org.folio.search.domain.dto.ConsortiumHoldingCollection; +import org.folio.search.domain.dto.ConsortiumItem; +import org.folio.search.domain.dto.ConsortiumItemCollection; +import org.folio.search.domain.dto.Instance; +import org.folio.search.model.service.CqlSearchRequest; +import org.folio.search.service.SearchService; +import org.folio.spring.FolioExecutionContext; +import org.springframework.stereotype.Service; + +/** + * Class designed to be executed only in scope of consortium central tenant id. + * So, it can be expected to always have central tenant id in {@link FolioExecutionContext}. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public class ConsortiumInstanceSearchService { + + private final SearchService searchService; + + public ConsortiumHolding getConsortiumHolding(String id, CqlSearchRequest searchRequest) { + var result = searchService.search(searchRequest); + + if (isEmpty(result.getRecords()) || isEmpty(result.getRecords().iterator().next().getHoldings())) { + return new ConsortiumHolding(); + } + + var instance = result.getRecords().iterator().next(); + var holding = instance.getHoldings().stream() + .filter(hol -> Objects.equals(id, hol.getId())) + .findFirst().orElse(null); + + if (holding == null) { + return new ConsortiumHolding(); + } + + return ConsortiumHoldingMapper.toConsortiumHolding(instance.getId(), holding); + } + + public ConsortiumItem getConsortiumItem(String id, CqlSearchRequest searchRequest) { + var result = searchService.search(searchRequest); + + if (isEmpty(result.getRecords()) || isEmpty(result.getRecords().iterator().next().getItems())) { + return new ConsortiumItem(); + } + + var instance = result.getRecords().iterator().next(); + var item = instance.getItems().stream() + .filter(it -> Objects.equals(id, it.getId())) + .findFirst().orElse(null); + + if (item == null) { + return new ConsortiumItem(); + } + + return ConsortiumItemMapper.toConsortiumItem(instance.getId(), item); + } + + public ConsortiumHoldingCollection fetchConsortiumBatchHoldings(CqlSearchRequest searchRequest, + Set ids) { + var result = searchService.search(searchRequest); + var consortiumHoldings = result.getRecords().stream() + .flatMap(instance -> + instance.getHoldings().stream() + .filter(holding -> ids.contains(holding.getId())) + .map(holding -> ConsortiumHoldingMapper.toConsortiumHolding(instance.getId(), holding)) + ) + .toList(); + return new ConsortiumHoldingCollection() + .holdings(consortiumHoldings) + .totalRecords(consortiumHoldings.size()); + } + + public ConsortiumItemCollection fetchConsortiumBatchItems(CqlSearchRequest searchRequest, + Set ids) { + var result = searchService.search(searchRequest); + var consortiumItems = result.getRecords().stream() + .flatMap(instance -> + instance.getItems().stream() + .filter(item -> ids.contains(item.getId())) + .map(item -> ConsortiumItemMapper.toConsortiumItem(instance.getId(), item)) + ) + .toList(); + + return new ConsortiumItemCollection() + .items(consortiumItems) + .totalRecords(consortiumItems.size()); + } +} diff --git a/src/main/java/org/folio/search/utils/SearchUtils.java b/src/main/java/org/folio/search/utils/SearchUtils.java index ebfbcaf48..2435e17e8 100644 --- a/src/main/java/org/folio/search/utils/SearchUtils.java +++ b/src/main/java/org/folio/search/utils/SearchUtils.java @@ -360,6 +360,13 @@ public static String normalizeToAlphaNumeric(String value) { .orElse(null); } + /** + * Build preference string to address similar requests to the same shard. + * */ + public static String buildPreferenceKey(String tenantId, String resource, String query) { + return tenantId + "-" + resource + "-" + query; + } + private static Object getMultilangValueObject(Object value) { return value instanceof MultilangValue v ? v.getMultilangValues() : value; } diff --git a/src/main/resources/model/instance.json b/src/main/resources/model/instance.json index 09c59287d..cd57246ce 100644 --- a/src/main/resources/model/instance.json +++ b/src/main/resources/model/instance.json @@ -273,6 +273,9 @@ "index": "keyword", "showInResponse": [ "call-number-browse" ] }, + "holdingsRecordId": { + "index": "keyword" + }, "hrid": { "index": "keyword_lowercase" }, diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index d0c347cff..5a158fef0 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -76,6 +76,18 @@ paths: /search/consortium/locations: $ref: 'paths/search-consortium/search-consortium-locations.yaml' + /search/consortium/batch/items: + $ref: 'paths/search-consortium/search-consortium-batch-items.yaml' + + /search/consortium/batch/holdings: + $ref: 'paths/search-consortium/search-consortium-batch-holdings.yaml' + + /search/consortium/holding/{id}: + $ref: 'paths/search-consortium/search-consortium-holding.yaml' + + /search/consortium/item/{id}: + $ref: 'paths/search-consortium/search-consortium-item.yaml' + /search/index/indices: $ref: 'paths/search-index/search-index-indices.yaml' @@ -125,5 +137,3 @@ paths: $ref: 'paths/browse-config/browse-type-browse-option-id.yaml' - - diff --git a/src/main/resources/swagger.api/parameters/batchIdsDto.yaml b/src/main/resources/swagger.api/parameters/batchIdsDto.yaml new file mode 100644 index 000000000..367ec45b8 --- /dev/null +++ b/src/main/resources/swagger.api/parameters/batchIdsDto.yaml @@ -0,0 +1,11 @@ +description: Batch IDs DTO +type: object +properties: + ids: + description: Entity IDs + type: array + items: + type: string + format: uuid +required: + - ids diff --git a/src/main/resources/swagger.api/parameters/record-id-param.yaml b/src/main/resources/swagger.api/parameters/record-id-param.yaml new file mode 100644 index 000000000..cc6f20222 --- /dev/null +++ b/src/main/resources/swagger.api/parameters/record-id-param.yaml @@ -0,0 +1,7 @@ +name: id +description: The UUID of a record +required: true +in: path +schema: + type: string + format: uuid diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml new file mode 100644 index 000000000..c60807ebf --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-holdings.yaml @@ -0,0 +1,27 @@ +post: + operationId: fetchConsortiumBatchHoldings + summary: Fetch Consolidated Holdings + description: Fetch a list of holdings (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + requestBody: + content: + application/json: + schema: + $ref: '../../parameters/batchIdsDto.yaml' + responses: + '200': + description: List of holdings + content: + application/json: + schema: + $ref: '../../schemas/entity/consortiumHoldingCollection.yaml' + examples: + consortiumItemCollection: + $ref: '../../examples/response/consortiumHoldingsCollectionResponse.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml new file mode 100644 index 000000000..0f2dff213 --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-batch-items.yaml @@ -0,0 +1,27 @@ +post: + operationId: fetchConsortiumBatchItems + summary: Fetch Consolidated Items + description: Fetch a list of items (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + requestBody: + content: + application/json: + schema: + $ref: '../../parameters/batchIdsDto.yaml' + responses: + '200': + description: List of items + content: + application/json: + schema: + $ref: '../../schemas/entity/consortiumItemCollection.yaml' + examples: + consortiumItemCollection: + $ref: '../../examples/response/consortiumItemCollectionResponse.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml new file mode 100644 index 000000000..ed00f7981 --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-holding.yaml @@ -0,0 +1,20 @@ +get: + operationId: getConsortiumHolding + summary: Fetch consolidated holding by id + description: Get holding by id (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '../../parameters/record-id-param.yaml' + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + responses: + '200': + description: An instance holding + content: + application/json: + schema: + $ref: '../../schemas/entity/consortiumHolding.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' \ No newline at end of file diff --git a/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml new file mode 100644 index 000000000..a33b183d9 --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-consortium/search-consortium-item.yaml @@ -0,0 +1,20 @@ +get: + operationId: getConsortiumItem + summary: Fetch consolidated item by id + description: Get an item (only for consortium environment) + tags: + - search-consortium + parameters: + - $ref: '../../parameters/record-id-param.yaml' + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + responses: + '200': + description: An instance item + content: + application/json: + schema: + $ref: '../../schemas/entity/consortiumItem.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' \ No newline at end of file diff --git a/src/test/java/org/folio/search/controller/ConsortiumSearchHoldingsIT.java b/src/test/java/org/folio/search/controller/ConsortiumSearchHoldingsIT.java index ef50d933e..288143b14 100644 --- a/src/test/java/org/folio/search/controller/ConsortiumSearchHoldingsIT.java +++ b/src/test/java/org/folio/search/controller/ConsortiumSearchHoldingsIT.java @@ -5,6 +5,8 @@ import static org.folio.search.model.Pair.pair; import static org.folio.search.sample.SampleInstances.getSemanticWeb; import static org.folio.search.sample.SampleInstances.getSemanticWebId; +import static org.folio.search.support.base.ApiEndpoints.consortiumBatchHoldingsSearchPath; +import static org.folio.search.support.base.ApiEndpoints.consortiumHoldingSearchPath; import static org.folio.search.support.base.ApiEndpoints.consortiumHoldingsSearchPath; import static org.folio.search.utils.TestConstants.CENTRAL_TENANT_ID; import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID; @@ -14,7 +16,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Arrays; import java.util.List; +import java.util.UUID; +import org.folio.search.domain.dto.BatchIdsDto; import org.folio.search.domain.dto.ConsortiumHolding; import org.folio.search.domain.dto.ConsortiumHoldingCollection; import org.folio.search.model.Pair; @@ -91,6 +96,51 @@ void tryGetConsortiumHoldings_returns400_whenOrderBySpecifiedWithoutAnyFilters() .andExpect(jsonPath("$.errors[0].code", is("validation_error"))); } + @Test + void doGetConsortiumHolding_returns200AndRecord() { + var holdings = getExpectedConsolidatedHoldings(); + var expectedHolding = holdings[0]; + var result = doGet(consortiumHoldingSearchPath(expectedHolding.getId()), CENTRAL_TENANT_ID); + var actual = parseResponse(result, ConsortiumHolding.class); + + assertThat(actual) + .isEqualTo(expectedHolding); + } + + @Test + void tryGetConsortiumHolding_returns400_whenRequestedForNotCentralTenant() throws Exception { + tryGet(consortiumHoldingSearchPath(UUID.randomUUID().toString())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].message", is(REQUEST_NOT_ALLOWED_MSG))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("x-okapi-tenant"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is(MEMBER_TENANT_ID))); + } + + @Test + void doGetConsortiumBatchHoldings_returns200AndRecords() { + var holdings = getExpectedConsolidatedHoldings(); + var request = new BatchIdsDto() + .ids(Arrays.stream(holdings).map(ConsortiumHolding::getId).map(UUID::fromString).toList()); + var result = doPost(consortiumBatchHoldingsSearchPath(), CENTRAL_TENANT_ID, request); + var actual = parseResponse(result, ConsortiumHoldingCollection.class); + + assertThat(actual.getTotalRecords()).isEqualTo(3); + assertThat(actual.getHoldings()).containsExactlyInAnyOrder(holdings); + } + + @Test + void tryGetConsortiumBatchHoldings_returns400_whenRequestedForNotCentralTenant() throws Exception { + tryPost(consortiumBatchHoldingsSearchPath(), new BatchIdsDto().ids(List.of(UUID.randomUUID()))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].message", is(REQUEST_NOT_ALLOWED_MSG))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("x-okapi-tenant"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is(MEMBER_TENANT_ID))); + } + private ConsortiumHolding[] getExpectedHoldings() { var instance = getSemanticWeb(); return instance.getHoldings().stream() @@ -107,4 +157,17 @@ private ConsortiumHolding[] getExpectedHoldings() { .discoverySuppress(holding.getDiscoverySuppress() != null && holding.getDiscoverySuppress())) .toArray(ConsortiumHolding[]::new); } + + private ConsortiumHolding[] getExpectedConsolidatedHoldings() { + var instance = getSemanticWeb(); + return instance.getHoldings().stream() + .map(holding -> new ConsortiumHolding() + .id(holding.getId()) + .hrid(holding.getHrid()) + .tenantId(MEMBER_TENANT_ID) + .instanceId(instance.getId()) + .permanentLocationId(holding.getPermanentLocationId()) + .discoverySuppress(holding.getDiscoverySuppress() != null && holding.getDiscoverySuppress())) + .toArray(ConsortiumHolding[]::new); + } } diff --git a/src/test/java/org/folio/search/controller/ConsortiumSearchItemsIT.java b/src/test/java/org/folio/search/controller/ConsortiumSearchItemsIT.java index ac20c4690..40668512d 100644 --- a/src/test/java/org/folio/search/controller/ConsortiumSearchItemsIT.java +++ b/src/test/java/org/folio/search/controller/ConsortiumSearchItemsIT.java @@ -5,6 +5,8 @@ import static org.folio.search.model.Pair.pair; import static org.folio.search.sample.SampleInstances.getSemanticWeb; import static org.folio.search.sample.SampleInstances.getSemanticWebId; +import static org.folio.search.support.base.ApiEndpoints.consortiumBatchItemsSearchPath; +import static org.folio.search.support.base.ApiEndpoints.consortiumItemSearchPath; import static org.folio.search.support.base.ApiEndpoints.consortiumItemsSearchPath; import static org.folio.search.utils.TestConstants.CENTRAL_TENANT_ID; import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID; @@ -14,7 +16,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Arrays; import java.util.List; +import java.util.UUID; +import org.folio.search.domain.dto.BatchIdsDto; import org.folio.search.domain.dto.ConsortiumItem; import org.folio.search.domain.dto.ConsortiumItemCollection; import org.folio.search.model.Pair; @@ -95,6 +100,51 @@ void tryGetConsortiumItems_returns400_whenInstanceIdIsNotSpecified() throws Exce .andExpect(jsonPath("$.errors[0].code", is("validation_error"))); } + @Test + void doGetConsortiumItem_returns200AndRecords() { + var items = getExpectedItems(); + var expectedItem = items[0]; + var result = doGet(consortiumItemSearchPath(expectedItem.getId()), CENTRAL_TENANT_ID); + var actual = parseResponse(result, ConsortiumItem.class); + + assertThat(actual) + .isEqualTo(expectedItem); + } + + @Test + void tryGetConsortiumItem_returns400_whenRequestedForNotCentralTenant() throws Exception { + tryGet(consortiumItemSearchPath(UUID.randomUUID().toString())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].message", is(REQUEST_NOT_ALLOWED_MSG))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("x-okapi-tenant"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is(MEMBER_TENANT_ID))); + } + + @Test + void doGetConsortiumBatchItems_returns200AndRecords() { + var items = getExpectedItems(); + var request = new BatchIdsDto() + .ids(Arrays.stream(items).map(ConsortiumItem::getId).map(UUID::fromString).toList()); + var result = doPost(consortiumBatchItemsSearchPath(), CENTRAL_TENANT_ID, request); + var actual = parseResponse(result, ConsortiumItemCollection.class); + + assertThat(actual.getTotalRecords()).isEqualTo(3); + assertThat(actual.getItems()).containsExactlyInAnyOrder(items); + } + + @Test + void tryGetConsortiumBatchItems_returns400_whenRequestedForNotCentralTenant() throws Exception { + tryPost(consortiumBatchItemsSearchPath(), new BatchIdsDto().ids(List.of(UUID.randomUUID()))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].message", is(REQUEST_NOT_ALLOWED_MSG))) + .andExpect(jsonPath("$.errors[0].type", is("RequestValidationException"))) + .andExpect(jsonPath("$.errors[0].code", is("validation_error"))) + .andExpect(jsonPath("$.errors[0].parameters[0].key", is("x-okapi-tenant"))) + .andExpect(jsonPath("$.errors[0].parameters[0].value", is(MEMBER_TENANT_ID))); + } + private ConsortiumItem[] getExpectedItems() { var instance = getSemanticWeb(); return instance.getItems().stream() diff --git a/src/test/java/org/folio/search/service/SearchServiceTest.java b/src/test/java/org/folio/search/service/SearchServiceTest.java index eef05a6fc..badc7c741 100644 --- a/src/test/java/org/folio/search/service/SearchServiceTest.java +++ b/src/test/java/org/folio/search/service/SearchServiceTest.java @@ -73,7 +73,8 @@ void search_positive() { var expectedSearchResult = searchResult(TestResource.of(RESOURCE_ID)); when(searchFieldProvider.getSourceFields(RESOURCE_NAME, SEARCH)).thenReturn(new String[] {"field1", "field2"}); - when(cqlSearchQueryConverter.convertForConsortia(SEARCH_QUERY, RESOURCE_NAME)).thenReturn(searchSourceBuilder); + when(cqlSearchQueryConverter.convertForConsortia(SEARCH_QUERY, RESOURCE_NAME, false)) + .thenReturn(searchSourceBuilder); when(searchRepository.search(eq(searchRequest), eq(expectedSourceBuilder), anyString())).thenReturn(searchResponse); when(documentConverter.convertToSearchResult(searchResponse, TestResource.class)) .thenReturn(expectedSearchResult); @@ -100,7 +101,8 @@ void search_positive_withExpandAll() { .trackTotalHits(true).timeout(new TimeValue(1000, MILLISECONDS)); var expectedSearchResult = searchResult(TestResource.of(RESOURCE_ID)); - when(cqlSearchQueryConverter.convertForConsortia(SEARCH_QUERY, RESOURCE_NAME)).thenReturn(searchSourceBuilder); + when(cqlSearchQueryConverter.convertForConsortia(SEARCH_QUERY, RESOURCE_NAME, false)) + .thenReturn(searchSourceBuilder); when(searchRepository.search(eq(searchRequest), eq(expectedSourceBuilder), anyString())).thenReturn(searchResponse); when(documentConverter.convertToSearchResult(searchResponse, TestResource.class)) .thenReturn(expectedSearchResult); diff --git a/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java b/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java new file mode 100644 index 000000000..a7141b869 --- /dev/null +++ b/src/test/java/org/folio/search/service/consortium/ConsortiumInstanceSearchServiceTest.java @@ -0,0 +1,238 @@ +package org.folio.search.service.consortium; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.folio.search.utils.TestConstants.CENTRAL_TENANT_ID; +import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import org.folio.search.domain.dto.ConsortiumHolding; +import org.folio.search.domain.dto.ConsortiumHoldingCollection; +import org.folio.search.domain.dto.ConsortiumItem; +import org.folio.search.domain.dto.ConsortiumItemCollection; +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.model.SearchResult; +import org.folio.search.model.service.CqlSearchRequest; +import org.folio.search.service.SearchService; +import org.folio.spring.testing.type.UnitTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@UnitTest +@ExtendWith(MockitoExtension.class) +class ConsortiumInstanceSearchServiceTest { + + private @Mock SearchService searchService; + private @InjectMocks ConsortiumInstanceSearchService service; + + @Test + void getConsortiumHolding_positive() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var holdings = List.of(holding(UUID.randomUUID().toString(), CENTRAL_TENANT_ID), holding(id, MEMBER_TENANT_ID)); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).holdings(holdings))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumHolding(id, request); + + assertThat(result.getId()).isEqualTo(id); + assertThat(result.getTenantId()).isEqualTo(MEMBER_TENANT_ID); + assertThat(result.getInstanceId()).isEqualTo(instanceId); + } + + @Test + void getConsortiumHolding_positive_noRecords() { + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var searchResult = SearchResult.of(0, Collections.emptyList()); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumHolding(id, request); + + assertThat(result).isEqualTo(new ConsortiumHolding()); + } + + @Test + void getConsortiumHolding_positive_noHoldings() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var holdings = Collections.emptyList(); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).holdings(holdings))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumHolding(id, request); + + assertThat(result).isEqualTo(new ConsortiumHolding()); + } + + @Test + void getConsortiumHolding_positive_noDesiredHolding() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var holdings = List.of(holding(UUID.randomUUID().toString(), CENTRAL_TENANT_ID)); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).holdings(holdings))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumHolding(id, request); + + assertThat(result).isEqualTo(new ConsortiumHolding()); + } + + @Test + void getConsortiumItem_positive() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var items = List.of(item(UUID.randomUUID().toString(), CENTRAL_TENANT_ID), item(id, MEMBER_TENANT_ID)); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).items(items))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumItem(id, request); + + assertThat(result.getId()).isEqualTo(id); + assertThat(result.getTenantId()).isEqualTo(MEMBER_TENANT_ID); + assertThat(result.getInstanceId()).isEqualTo(instanceId); + assertThat(result.getHoldingsRecordId()).isNotBlank(); + } + + @Test + void getConsortiumItem_positive_noRecords() { + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var searchResult = SearchResult.of(0, Collections.emptyList()); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumItem(id, request); + + assertThat(result).isEqualTo(new ConsortiumItem()); + } + + @Test + void getConsortiumItem_positive_noItems() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var items = Collections.emptyList(); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).items(items))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumItem(id, request); + + assertThat(result).isEqualTo(new ConsortiumItem()); + } + + @Test + void getConsortiumItem_positive_noDesiredItem() { + var instanceId = UUID.randomUUID().toString(); + var id = UUID.randomUUID().toString(); + var request = Mockito.>mock(); + var items = List.of(item(UUID.randomUUID().toString(), CENTRAL_TENANT_ID)); + var searchResult = SearchResult.of(1, List.of(new Instance().id(instanceId).items(items))); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.getConsortiumItem(id, request); + + assertThat(result).isEqualTo(new ConsortiumItem()); + } + + @Test + void fetchConsortiumBatchHoldings_positive() { + var ids = List.of(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + var instances = List.of( + instanceForHoldings(List.of(holding(UUID.randomUUID().toString(), CENTRAL_TENANT_ID), + holding(ids.get(0), MEMBER_TENANT_ID))), + instanceForHoldings(List.of(holding(ids.get(1), CENTRAL_TENANT_ID))), + instanceForHoldings(List.of(holding(UUID.randomUUID().toString(), CENTRAL_TENANT_ID)))); + var searchResult = SearchResult.of(instances.size(), instances); + var request = Mockito.>mock(); + var expected = new ConsortiumHoldingCollection() + .holdings(instances.subList(0, instances.size() - 1).stream().map(instance -> { + var holding = instance.getHoldings().size() > 1 ? instance.getHoldings().get(1) : instance.getHoldings().get(0); + return new ConsortiumHolding() + .id(holding.getId()) + .instanceId(instance.getId()) + .discoverySuppress(false) + .tenantId(holding.getTenantId()); + }).toList()) + .totalRecords(2); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.fetchConsortiumBatchHoldings(request, Sets.newHashSet(ids)); + + assertThat(result).isEqualTo(expected); + } + + @Test + void fetchConsortiumBatchItems_positive() { + var ids = List.of(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + var instances = List.of( + instanceForItems(List.of(item(UUID.randomUUID().toString(), CENTRAL_TENANT_ID), + item(ids.get(0), MEMBER_TENANT_ID))), + instanceForItems(List.of(item(ids.get(1), CENTRAL_TENANT_ID))), + instanceForItems(List.of(item(UUID.randomUUID().toString(), CENTRAL_TENANT_ID)))); + var searchResult = SearchResult.of(instances.size(), instances); + var request = Mockito.>mock(); + var expected = new ConsortiumItemCollection() + .items(instances.subList(0, instances.size() - 1).stream().map(instance -> { + var item = instance.getItems().size() > 1 ? instance.getItems().get(1) : instance.getItems().get(0); + return new ConsortiumItem() + .id(item.getId()) + .instanceId(instance.getId()) + .tenantId(item.getTenantId()) + .holdingsRecordId(item.getHoldingsRecordId()); + }).toList()) + .totalRecords(2); + + when(searchService.search(request)).thenReturn(searchResult); + + var result = service.fetchConsortiumBatchItems(request, Sets.newHashSet(ids)); + + assertThat(result).isEqualTo(expected); + } + + private Holding holding(String id, String tenantId) { + return new Holding() + .id(id) + .tenantId(tenantId); + } + + private Item item(String id, String tenantId) { + return new Item() + .id(id) + .tenantId(tenantId) + .holdingsRecordId(UUID.randomUUID().toString()); + } + + private Instance instanceForHoldings(List holdings) { + return new Instance() + .id(UUID.randomUUID().toString()) + .holdings(holdings); + } + + private Instance instanceForItems(List items) { + return new Instance() + .id(UUID.randomUUID().toString()) + .items(items); + } +} 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 1309e2b69..dfc6d31c4 100644 --- a/src/test/java/org/folio/search/support/base/ApiEndpoints.java +++ b/src/test/java/org/folio/search/support/base/ApiEndpoints.java @@ -41,6 +41,22 @@ public static String consortiumItemsSearchPath(List> queryP return addQueryParams(consortiumItemsSearchPath(), queryParams); } + public static String consortiumHoldingSearchPath(String id) { + return "/search/consortium/holding/" + id; + } + + public static String consortiumItemSearchPath(String id) { + return "/search/consortium/item/" + id; + } + + public static String consortiumBatchHoldingsSearchPath() { + return "/search/consortium/batch/holdings"; + } + + public static String consortiumBatchItemsSearchPath() { + return "/search/consortium/batch/items"; + } + public static String authoritySearchPath() { return "/search/authorities"; } 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 980c0ddca..b56f35b4b 100644 --- a/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java +++ b/src/test/java/org/folio/search/support/base/BaseConsortiumIntegrationTest.java @@ -103,6 +103,25 @@ public static ResultActions doGet(MockHttpServletRequestBuilder request, String .andExpect(status().isOk()); } + @SneakyThrows + public static ResultActions tryPost(String uri, Object body) { + return tryPost(uri, MEMBER_TENANT_ID, body); + } + + @SneakyThrows + public static ResultActions tryPost(String uri, String tenantHeader, Object body) { + return mockMvc.perform(post(uri) + .content(asJsonString(body)) + .headers(defaultHeaders(tenantHeader)) + .accept("application/json;charset=UTF-8")); + } + + @SneakyThrows + public static ResultActions doPost(String uri, String tenantHeader, Object body) { + return tryPost(uri, tenantHeader, body) + .andExpect(status().isOk()); + } + @SneakyThrows protected static ResultActions doSearchByInstances(String query) { return doSearch(instanceSearchPath(), MEMBER_TENANT_ID, query, null, null, null); diff --git a/src/test/resources/samples/instance-response-sample/instance-full-response.json b/src/test/resources/samples/instance-response-sample/instance-full-response.json index 7f25f4f11..03c6c3f04 100644 --- a/src/test/resources/samples/instance-response-sample/instance-full-response.json +++ b/src/test/resources/samples/instance-response-sample/instance-full-response.json @@ -220,6 +220,7 @@ "staffOnly": false } ], + "holdingsRecordId" : "e3ff6133-b9a2-4d4c-a1c9-dc1867d4df19", "metadata": { "createdDate": "2020-12-08T15:47:21.327+00:00", "updatedDate": "2021-01-18T08:17:56.752+00:00", @@ -281,6 +282,7 @@ "staffOnly": false } ], + "holdingsRecordId" : "e3ff6133-b9a2-4d4c-a1c9-dc1867d4df19", "metadata": { "createdDate": "2020-12-08T15:47:22.827+00:00", "updatedDate": "2020-12-08T15:47:22.827+00:00" @@ -312,6 +314,7 @@ "notes": [], "statisticalCodeIds": [], "circulationNotes": [], + "holdingsRecordId" : "9550c935-401a-4a85-875e-4d1fe7678870", "metadata": { "createdDate": "2024-03-14T13:05:09.406+00:00", "createdByUserId": "93fc1367-7321-4836-b2a7-cbc7ffe8c20e",