From 8ba03f0a624f60cecd31e9d47f33bffc24e24319 Mon Sep 17 00:00:00 2001 From: Aleksei Pronichev Date: Tue, 18 Jun 2024 13:25:29 +0500 Subject: [PATCH] [MSEARCH-784] add bibframe authority index --- NEWS.md | 1 + README.md | 17 ++--- descriptors/ModuleDescriptor-template.json | 9 +++ .../search/controller/SearchController.java | 19 ++++++ .../integration/KafkaMessageListener.java | 17 +++++ .../BibframeAuthorityLabelProcessor.java | 14 ++++ .../BibframeAuthorityLccnProcessor.java | 37 ++++++++++ .../BibframeAuthorityTypeProcessor.java | 18 +++++ .../folio/search/utils/KafkaConstants.java | 1 + .../org/folio/search/utils/SearchUtils.java | 1 + src/main/resources/application.yml | 4 ++ .../index/bibframe-authority.json | 68 +++++++++++++++++++ .../resources/model/bibframe_authority.json | 45 ++++++++++++ .../result/bibframeSearchAuthorityResult.yaml | 12 ++++ .../resources/swagger.api/mod-search.yaml | 3 + .../search-bibframe-authorities.yaml | 25 +++++++ .../dto/bibframe/bibframeAuthority.yaml | 25 +++++++ .../bibframeSearchAuthorityResult.yaml | 20 ++++++ .../controller/SearchBibframeAuthorityIT.java | 58 ++++++++++++++++ .../folio/search/sample/SampleBibframe.java | 14 ++++ .../search/support/base/ApiEndpoints.java | 4 ++ .../support/base/BaseIntegrationTest.java | 13 ++++ .../org/folio/search/utils/TestConstants.java | 5 ++ src/test/resources/application.yml | 7 ++ .../samples/bibframe/authority_concept.json | 15 ++++ .../samples/bibframe/authority_person.json | 11 +++ 26 files changed, 455 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityLabelProcessor.java create mode 100644 src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityLccnProcessor.java create mode 100644 src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityTypeProcessor.java create mode 100644 src/main/resources/elasticsearch/index/bibframe-authority.json create mode 100644 src/main/resources/model/bibframe_authority.json create mode 100644 src/main/resources/swagger.api/examples/result/bibframeSearchAuthorityResult.yaml create mode 100644 src/main/resources/swagger.api/paths/search-bibframe/search-bibframe-authorities.yaml create mode 100644 src/main/resources/swagger.api/schemas/dto/bibframe/bibframeAuthority.yaml create mode 100644 src/main/resources/swagger.api/schemas/response/bibframeSearchAuthorityResult.yaml create mode 100644 src/test/java/org/folio/search/controller/SearchBibframeAuthorityIT.java create mode 100644 src/test/resources/samples/bibframe/authority_concept.json create mode 100644 src/test/resources/samples/bibframe/authority_person.json diff --git a/NEWS.md b/NEWS.md index 19bb87f46..3d62cb4cd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,6 +19,7 @@ * 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)) * Create bibframe index and process bibframe events ([MSEARCH-781](https://folio-org.atlassian.net/browse/MSEARCH-781)) +* Create bibframe authority index and process bibframe authority events ([MSEARCH-784](https://folio-org.atlassian.net/browse/MSEARCH-784)) * Allow Unified List of Inventory Locations in a Consortium to be fetched by member tenants ([MSEARCH-660](https://folio-org.atlassian.net/browse/MSEARCH-660)) * Implement Indexing of Campuses from Kafka ([MSEARCH-770](https://issues.folio.org/browse/MSEARCH-770)) * Extend response with additional Location fields for Inventory Locations in a Consortium endpoint ([MSEARCH-775](https://folio-org.atlassian.net/browse/MSEARCH-775)) diff --git a/README.md b/README.md index 8cc084a80..a8e142631 100644 --- a/README.md +++ b/README.md @@ -414,14 +414,15 @@ Consortium feature on module enable is defined by 'centralTenantId' tenant param ### Search API -| METHOD | URL | DESCRIPTION | -|:-------|:------------------------------|:-------------------------------------------------------------------------------------| -| GET | `/search/instances` | Search by instances and to this instance items and holding-records | -| GET | `/search/authorities` | Search by authority records | -| GET | `/search/bibframe` | Search linked data graph resource descriptions | -| GET | `/search/{recordType}/facets` | Get facets where recordType could be: instances, authorities, contributors, subjects | -| GET | ~~`/search/instances/ids`~~ | (DEPRECATED) Stream instance ids as JSON or plain text | -| GET | ~~`/search/holdings/ids`~~ | (DEPRECATED) Stream holding record ids as JSON or plain text | +| METHOD | URL | DESCRIPTION | +|:-------|:------------------------------------------|:-------------------------------------------------------------------------------------| +| GET | `/search/instances` | Search by instances and to this instance items and holding-records | +| GET | `/search/authorities` | Search by authority records | +| GET | `/search/bibframe` | Search linked data graph resource descriptions | +| GET | `/search/bibframe/authorities` | Search linked data graph authority resource descriptions | +| GET | `/search/{recordType}/facets` | Get facets where recordType could be: instances, authorities, contributors, subjects | +| GET | ~~`/search/instances/ids`~~ | (DEPRECATED) Stream instance ids as JSON or plain text | +| GET | ~~`/search/holdings/ids`~~ | (DEPRECATED) Stream holding record ids as JSON or plain text | #### Searching and filtering diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index e741c054a..f4909dafa 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -111,6 +111,15 @@ "search.bibframe.collection.get" ] }, + { + "methods": [ + "GET" + ], + "pathPattern": "/search/bibframe/authorities", + "permissionsRequired": [ + "search.bibframe.collection.get" + ] + }, { "methods": [ "GET" diff --git a/src/main/java/org/folio/search/controller/SearchController.java b/src/main/java/org/folio/search/controller/SearchController.java index 7813c6165..a8c9b8ac2 100644 --- a/src/main/java/org/folio/search/controller/SearchController.java +++ b/src/main/java/org/folio/search/controller/SearchController.java @@ -4,6 +4,8 @@ import org.folio.search.domain.dto.Authority; import org.folio.search.domain.dto.AuthoritySearchResult; import org.folio.search.domain.dto.Bibframe; +import org.folio.search.domain.dto.BibframeAuthority; +import org.folio.search.domain.dto.BibframeSearchAuthorityResult; import org.folio.search.domain.dto.BibframeSearchResult; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.InstanceSearchResult; @@ -65,6 +67,23 @@ public ResponseEntity searchBibframe(String tenant, String ); } + @Override + public ResponseEntity searchBibframeAuthorities(String tenant, + String query, + Integer limit, + Integer offset) { + var searchRequest = CqlSearchRequest.of( + BibframeAuthority.class, tenant, query, limit, offset, true); + var result = searchService.search(searchRequest); + return ResponseEntity.ok(new BibframeSearchAuthorityResult() + .searchQuery(query) + .content(result.getRecords()) + .pageNumber(divPlusOneIfRemainder(offset, limit)) + .totalPages(divPlusOneIfRemainder(result.getTotalRecords(), limit)) + .totalRecords(result.getTotalRecords()) + ); + } + private int divPlusOneIfRemainder(int one, int two) { var modulo = one % two; return one / two + (modulo > 0 ? 1 : 0); diff --git a/src/main/java/org/folio/search/integration/KafkaMessageListener.java b/src/main/java/org/folio/search/integration/KafkaMessageListener.java index f580bdd84..997bae5c2 100644 --- a/src/main/java/org/folio/search/integration/KafkaMessageListener.java +++ b/src/main/java/org/folio/search/integration/KafkaMessageListener.java @@ -10,6 +10,7 @@ import static org.folio.search.utils.SearchConverterUtils.getEventPayload; import static org.folio.search.utils.SearchConverterUtils.getResourceEventId; import static org.folio.search.utils.SearchConverterUtils.getResourceSource; +import static org.folio.search.utils.SearchUtils.BIBFRAME_AUTHORITY_RESOURCE; import static org.folio.search.utils.SearchUtils.BIBFRAME_RESOURCE; import static org.folio.search.utils.SearchUtils.ID_FIELD; import static org.folio.search.utils.SearchUtils.INSTANCE_ID_FIELD; @@ -212,6 +213,22 @@ public void handleBibframeEvents(List> con indexResources(batch, resourceService::indexResources); } + @KafkaListener( + id = KafkaConstants.BIBFRAME_AUTHORITY_LISTENER_ID, + containerFactory = "standardListenerContainerFactory", + groupId = "#{folioKafkaProperties.listener['bibframe-authorities'].groupId}", + concurrency = "#{folioKafkaProperties.listener['bibframe-authorities'].concurrency}", + topicPattern = "#{folioKafkaProperties.listener['bibframe-authorities'].topicPattern}") + public void handleBibframeAuthorityEvents(List> consumerRecords) { + log.info("Processing bibframe authority events from Kafka [number of events: {}]", consumerRecords.size()); + var batch = consumerRecords.stream() + .map(ConsumerRecord::value) + .map(bibAuthority -> bibAuthority.resourceName(BIBFRAME_AUTHORITY_RESOURCE).id(getResourceEventId(bibAuthority))) + .toList(); + + indexResources(batch, resourceService::indexResources); + } + private void indexResources(List batch, Consumer> indexConsumer) { var batchByTenant = batch.stream().collect(Collectors.groupingBy(ResourceEvent::getTenant)); diff --git a/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityLabelProcessor.java b/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityLabelProcessor.java new file mode 100644 index 000000000..ab8d90e96 --- /dev/null +++ b/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityLabelProcessor.java @@ -0,0 +1,14 @@ +package org.folio.search.service.setter.bibframe.authority; + +import org.folio.search.domain.dto.BibframeAuthority; +import org.folio.search.service.setter.FieldProcessor; +import org.springframework.stereotype.Component; + +@Component +public class BibframeAuthorityLabelProcessor implements FieldProcessor { + + @Override + public String getFieldValue(BibframeAuthority bibframe) { + return bibframe.getLabel(); + } +} diff --git a/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityLccnProcessor.java b/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityLccnProcessor.java new file mode 100644 index 000000000..8c910e5e3 --- /dev/null +++ b/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityLccnProcessor.java @@ -0,0 +1,37 @@ +package org.folio.search.service.setter.bibframe.authority; + +import static java.util.stream.Collectors.toCollection; +import static org.folio.search.domain.dto.BibframeAuthorityIdentifiersInner.TypeEnum.LCCN; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.folio.search.domain.dto.BibframeAuthority; +import org.folio.search.domain.dto.BibframeAuthorityIdentifiersInner; +import org.folio.search.service.lccn.LccnNormalizer; +import org.folio.search.service.setter.FieldProcessor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class BibframeAuthorityLccnProcessor implements FieldProcessor> { + + private final LccnNormalizer lccnNormalizer; + + @Override + public Set getFieldValue(BibframeAuthority bibframe) { + return Optional.of(bibframe) + .map(BibframeAuthority::getIdentifiers) + .orElseGet(Collections::emptyList) + .stream() + .filter(i -> LCCN.equals(i.getType())) + .map(BibframeAuthorityIdentifiersInner::getValue) + .filter(Objects::nonNull) + .map(lccnNormalizer) + .flatMap(Optional::stream) + .collect(toCollection(LinkedHashSet::new)); + } +} diff --git a/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityTypeProcessor.java b/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityTypeProcessor.java new file mode 100644 index 000000000..70b3fb375 --- /dev/null +++ b/src/main/java/org/folio/search/service/setter/bibframe/authority/BibframeAuthorityTypeProcessor.java @@ -0,0 +1,18 @@ +package org.folio.search.service.setter.bibframe.authority; + +import org.folio.search.domain.dto.BibframeAuthority; +import org.folio.search.service.setter.FieldProcessor; +import org.springframework.stereotype.Component; + +@Component +public class BibframeAuthorityTypeProcessor implements FieldProcessor { + + @Override + public String getFieldValue(BibframeAuthority bibframe) { + var type = bibframe.getType(); + if (type == null) { + return null; + } + return type.toString(); + } +} diff --git a/src/main/java/org/folio/search/utils/KafkaConstants.java b/src/main/java/org/folio/search/utils/KafkaConstants.java index d192c4245..38ec41432 100644 --- a/src/main/java/org/folio/search/utils/KafkaConstants.java +++ b/src/main/java/org/folio/search/utils/KafkaConstants.java @@ -9,6 +9,7 @@ public final class KafkaConstants { public static final String CLASSIFICATION_TYPE_LISTENER_ID = "mod-search-classification-type-listener"; public static final String LOCATION_LISTENER_ID = "mod-search-location-listener"; public static final String BIBFRAME_LISTENER_ID = "mod-search-bibframe-listener"; + public static final String BIBFRAME_AUTHORITY_LISTENER_ID = "mod-search-bibframe-authorities-listener"; private KafkaConstants() {} } diff --git a/src/main/java/org/folio/search/utils/SearchUtils.java b/src/main/java/org/folio/search/utils/SearchUtils.java index 2f44b7822..474d88b0d 100644 --- a/src/main/java/org/folio/search/utils/SearchUtils.java +++ b/src/main/java/org/folio/search/utils/SearchUtils.java @@ -39,6 +39,7 @@ public class SearchUtils { public static final String LOCATION_RESOURCE = "location"; public static final String CLASSIFICATION_TYPE_RESOURCE = "classification-type"; public static final String BIBFRAME_RESOURCE = "bibframe"; + public static final String BIBFRAME_AUTHORITY_RESOURCE = "bibframe-authority"; public static final String CAMPUS_RESOURCE = "campus"; public static final String ID_FIELD = "id"; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e79d2c6df..c57b33187 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -139,6 +139,10 @@ folio: concurrency: ${KAFKA_BIBFRAME_CONCURRENCY:1} topic-pattern: (${folio.environment}\.)(.*\.)search\.bibframe group-id: ${folio.environment}-mod-search-bibframe-group + bibframe-authorities: + concurrency: ${KAFKA_BIBFRAME_CONCURRENCY:1} + topic-pattern: (${folio.environment}\.)(.*\.)search\.bibframe-authorities + group-id: ${folio.environment}-mod-search-bibframe-authorities-group okapiUrl: ${okapi.url} logging: request: diff --git a/src/main/resources/elasticsearch/index/bibframe-authority.json b/src/main/resources/elasticsearch/index/bibframe-authority.json new file mode 100644 index 000000000..137a49b25 --- /dev/null +++ b/src/main/resources/elasticsearch/index/bibframe-authority.json @@ -0,0 +1,68 @@ +{ + "index": { + "number_of_shards": 4, + "number_of_replicas": 2, + "refresh_interval": "1s", + "codec": "best_compression", + "mapping.total_fields.limit": 1000 + }, + "analysis": { + "filter": { + "folio_word_delimiter_graph": { + "type": "word_delimiter_graph", + "catenate_words": true + } + }, + "normalizer": { + "keyword_lowercase": { + "filter": [ + "lowercase", + "trim" + ], + "type": "custom" + }, + "keyword_uppercase": { + "filter": [ + "uppercase", + "trim" + ], + "type": "custom" + }, + "keyword_trimmed": { + "filter": [ + "trim" + ], + "type": "custom" + } + }, + "analyzer": { + "source_analyzer": { + "tokenizer": "icu_tokenizer", + "filter": [ + "folio_word_delimiter_graph", + "icu_folding" + ], + "char_filter": [ + "and_char_replacement" + ], + "type": "custom" + }, + "whitespace_lowercase_analyzer": { + "tokenizer": "whitespace", + "filter": [ + "lowercase", + "icu_folding" + ], + "type": "custom" + } + }, + "tokenizers": { }, + "char_filter": { + "and_char_replacement": { + "type": "pattern_replace", + "pattern": " & ", + "replacement": " and " + } + } + } +} diff --git a/src/main/resources/model/bibframe_authority.json b/src/main/resources/model/bibframe_authority.json new file mode 100644 index 000000000..8795ad603 --- /dev/null +++ b/src/main/resources/model/bibframe_authority.json @@ -0,0 +1,45 @@ +{ + "name": "bibframe-authority", + "eventBodyJavaClass": "org.folio.search.domain.dto.BibframeAuthority", + "languageSourcePaths": ["$.languages"], + "fields": { + "id": { + "index": "keyword" + }, + "label": { + "index": "whitespace" + }, + "type": { + "index": "keyword" + }, + "identifiers": { + "type": "object", + "properties": { + "value": { + "index": "whitespace" + }, + "type": { + "index": "whitespace" + } + } + } + }, + "searchFields": { + "label": { + "type": "search", + "index": "multilang", + "processor": "bibframeAuthorityLabelProcessor" + }, + "type": { + "type": "search", + "index": "keyword", + "processor": "bibframeAuthorityTypeProcessor" + }, + "lccn": { + "type": "search", + "index": "keyword", + "processor": "bibframeAuthorityLccnProcessor" + } + }, + "indexMappings": { } +} diff --git a/src/main/resources/swagger.api/examples/result/bibframeSearchAuthorityResult.yaml b/src/main/resources/swagger.api/examples/result/bibframeSearchAuthorityResult.yaml new file mode 100644 index 000000000..a35956d7b --- /dev/null +++ b/src/main/resources/swagger.api/examples/result/bibframeSearchAuthorityResult.yaml @@ -0,0 +1,12 @@ +value: + searchQuery: "query string" + content: + - id: "1" + label: "Label Value" + type: "Person" + identifiers: + - value: "sh85121033" + type: "LCCN" + pageNumber: 0 + totalPages: 3 + totalRecords: 27 diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml index 71390f7e3..85d1a2267 100644 --- a/src/main/resources/swagger.api/mod-search.yaml +++ b/src/main/resources/swagger.api/mod-search.yaml @@ -118,6 +118,9 @@ paths: /search/bibframe: $ref: 'paths/search-bibframe/search-bibframe.yaml' + /search/bibframe/authorities: + $ref: 'paths/search-bibframe/search-bibframe-authorities.yaml' + /browse/call-numbers/instances: $ref: 'paths/browse-call-numbers/browse-call-numbers-instances.yaml' diff --git a/src/main/resources/swagger.api/paths/search-bibframe/search-bibframe-authorities.yaml b/src/main/resources/swagger.api/paths/search-bibframe/search-bibframe-authorities.yaml new file mode 100644 index 000000000..3035a98b6 --- /dev/null +++ b/src/main/resources/swagger.api/paths/search-bibframe/search-bibframe-authorities.yaml @@ -0,0 +1,25 @@ +get: + operationId: searchBibframeAuthorities + summary: Search Bibframe Authorities + description: Get a list of bibframe authorities records for CQL query + tags: + - search + parameters: + - $ref: '../../parameters/x-okapi-tenant-header.yaml' + - $ref: '../../parameters/cql-query.yaml' + - $ref: '../../parameters/bibframe-limit-param.yaml' + - $ref: '../../parameters/offset-param.yaml' + responses: + '200': + description: 'Bibframe authorities search result' + content: + application/json: + schema: + $ref: '../../schemas/response/bibframeSearchAuthorityResult.yaml' + examples: + searchResult: + $ref: '../../examples/result/bibframeSearchAuthorityResult.yaml' + '400': + $ref: '../../responses/badRequestResponse.yaml' + '500': + $ref: '../../responses/internalServerErrorResponse.yaml' diff --git a/src/main/resources/swagger.api/schemas/dto/bibframe/bibframeAuthority.yaml b/src/main/resources/swagger.api/schemas/dto/bibframe/bibframeAuthority.yaml new file mode 100644 index 000000000..8e39d0e11 --- /dev/null +++ b/src/main/resources/swagger.api/schemas/dto/bibframe/bibframeAuthority.yaml @@ -0,0 +1,25 @@ +description: "Bibframe authority search dto, contains Authority and Identifiers" +type: "object" +properties: + id: + description: "The Linked Data ID of an Authority" + type: "string" + label: + description: "Value of Label" + type: "string" + type: + type: "string" + identifiers: + type: "array" + description: "Authority identifier array" + items: + properties: + value: + type: "string" + description: "Value of Identifier" + type: + type: "string" + enum: + - "LCCN" +required: + - "id" diff --git a/src/main/resources/swagger.api/schemas/response/bibframeSearchAuthorityResult.yaml b/src/main/resources/swagger.api/schemas/response/bibframeSearchAuthorityResult.yaml new file mode 100644 index 000000000..dbb62830d --- /dev/null +++ b/src/main/resources/swagger.api/schemas/response/bibframeSearchAuthorityResult.yaml @@ -0,0 +1,20 @@ +description: "Bibframe authority search result response" +type: "object" +properties: + searchQuery: + type: "string" + description: "Initial search query" + content: + type: "array" + description: "List of bibframe authority records found" + items: + $ref: "../../schemas/dto/bibframe/bibframeAuthority.yaml" + pageNumber: + type: "integer" + description: "Current results page number, 0 by default" + totalPages: + type: "integer" + description: "Total pages count" + totalRecords: + type: "integer" + description: "Total results count" diff --git a/src/test/java/org/folio/search/controller/SearchBibframeAuthorityIT.java b/src/test/java/org/folio/search/controller/SearchBibframeAuthorityIT.java new file mode 100644 index 000000000..d220dd276 --- /dev/null +++ b/src/test/java/org/folio/search/controller/SearchBibframeAuthorityIT.java @@ -0,0 +1,58 @@ +package org.folio.search.controller; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +import org.folio.search.domain.dto.BibframeAuthority; +import org.folio.search.sample.SampleBibframe; +import org.folio.search.support.base.BaseIntegrationTest; +import org.folio.spring.testing.type.IntegrationTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +@IntegrationTest +class SearchBibframeAuthorityIT extends BaseIntegrationTest { + + @BeforeAll + static void prepare() { + setUpTenant(BibframeAuthority.class, 2, + SampleBibframe.getBibframeAuthorityConceptSampleAsMap(), SampleBibframe.getBibframeAuthorityPersonSampleAsMap() + ); + } + + @AfterAll + static void cleanUp() { + removeTenant(); + } + + @DisplayName("search by authority bibframe (all authorities are found)") + @ParameterizedTest(name = "[{0}] {2}") + @CsvSource({ + " 1, 2, label any \"*\"", + " 2, 2, label any \"*-*\"", + " 3, 2, label any \"*-label\"", + " 4, 2, label <> \"lab\"", + " 5, 1, label any \"concept-*\"", + " 6, 1, label = \"concept-label\"", + " 7, 1, label any \"person-*\"", + " 8, 1, label = \"person-label\"", + " 9, 1, type = \"PERSON\"", + "10, 1, type = \"CONCEPT\"", + "11, 1, lccn = \"sh9876543210\"", + "12, 0, lccn = \"s h9876543210\"", + "13, 1, lccn = \"sh0123456789\"", + "14, 0, lccn = \"sh 0123456789\"", + "15, 1, lccn = \"n0123456789\"", + "16, 0, lccn = \"n 0123456789\"", + "17, 2, lccn any \"*0123456789\"", + "18, 1, lccn any \"*0123456789\" AND type = \"PERSON\"", + "19, 1, lccn any \"*0123456789\" AND type = \"CONCEPT\"" + }) + void searchByBibframe_parameterized_singleResult(int index, int size, String query) throws Throwable { + doSearchByBibframeAuthority(query) + .andExpect(jsonPath("$.totalRecords", is(size))); + } +} diff --git a/src/test/java/org/folio/search/sample/SampleBibframe.java b/src/test/java/org/folio/search/sample/SampleBibframe.java index 56962f4d0..0fc0355b6 100644 --- a/src/test/java/org/folio/search/sample/SampleBibframe.java +++ b/src/test/java/org/folio/search/sample/SampleBibframe.java @@ -16,6 +16,12 @@ public class SampleBibframe { private static final Map BIBFRAME_2_AS_MAP = readJsonFromFile("/samples/bibframe/bibframe2.json", MAP_TYPE_REFERENCE); + private static final Map BIBFRAME_AUTHORITY_CONCEPT_AS_MAP = + readJsonFromFile("/samples/bibframe/authority_concept.json", MAP_TYPE_REFERENCE); + + private static final Map BIBFRAME_AUTHORITY_PERSON_AS_MAP = + readJsonFromFile("/samples/bibframe/authority_person.json", MAP_TYPE_REFERENCE); + public static Map getBibframeSampleAsMap() { return BIBFRAME_AS_MAP; } @@ -23,4 +29,12 @@ public static Map getBibframeSampleAsMap() { public static Map getBibframe2SampleAsMap() { return BIBFRAME_2_AS_MAP; } + + public static Map getBibframeAuthorityConceptSampleAsMap() { + return BIBFRAME_AUTHORITY_CONCEPT_AS_MAP; + } + + public static Map getBibframeAuthorityPersonSampleAsMap() { + return BIBFRAME_AUTHORITY_PERSON_AS_MAP; + } } 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 5f8c14f3d..b3fe38d00 100644 --- a/src/test/java/org/folio/search/support/base/ApiEndpoints.java +++ b/src/test/java/org/folio/search/support/base/ApiEndpoints.java @@ -81,6 +81,10 @@ public static String bibframeSearchPath() { return "/search/bibframe"; } + public static String bibframeAuthoritySearchPath() { + return "/search/bibframe/authorities"; + } + public static String authorityBrowsePath() { return "/browse/authorities"; } diff --git a/src/test/java/org/folio/search/support/base/BaseIntegrationTest.java b/src/test/java/org/folio/search/support/base/BaseIntegrationTest.java index 96c644e06..5059a9888 100644 --- a/src/test/java/org/folio/search/support/base/BaseIntegrationTest.java +++ b/src/test/java/org/folio/search/support/base/BaseIntegrationTest.java @@ -5,10 +5,12 @@ import static org.awaitility.Durations.TWO_HUNDRED_MILLISECONDS; import static org.awaitility.Durations.TWO_MINUTES; import static org.folio.search.support.base.ApiEndpoints.authoritySearchPath; +import static org.folio.search.support.base.ApiEndpoints.bibframeAuthoritySearchPath; import static org.folio.search.support.base.ApiEndpoints.bibframeSearchPath; import static org.folio.search.support.base.ApiEndpoints.instanceSearchPath; import static org.folio.search.utils.SearchUtils.getIndexName; import static org.folio.search.utils.TestConstants.TENANT_ID; +import static org.folio.search.utils.TestConstants.bibframeAuthorityTopic; import static org.folio.search.utils.TestConstants.bibframeTopic; import static org.folio.search.utils.TestConstants.inventoryAuthorityTopic; import static org.folio.search.utils.TestUtils.asJsonString; @@ -40,6 +42,7 @@ import lombok.extern.log4j.Log4j2; import org.folio.search.domain.dto.Authority; import org.folio.search.domain.dto.Bibframe; +import org.folio.search.domain.dto.BibframeAuthority; import org.folio.search.domain.dto.FeatureConfig; import org.folio.search.domain.dto.Instance; import org.folio.search.domain.dto.ResourceEvent; @@ -164,6 +167,11 @@ protected static ResultActions doSearchByBibframe(String query) { return doSearch(bibframeSearchPath(), TENANT_ID, query, null, null, null); } + @SneakyThrows + protected static ResultActions doSearchByBibframeAuthority(String query) { + return doSearch(bibframeAuthoritySearchPath(), TENANT_ID, query, null, null, null); + } + @SneakyThrows protected static ResultActions attemptSearchByAuthorities(String query) { return attemptSearch(authoritySearchPath(), TENANT_ID, query, null, null, null); @@ -290,6 +298,11 @@ protected static void setUpTenant(Class type, String tenant, Runnable postIni setUpTenant(tenant, bibframeSearchPath(), postInitAction, asList(records), expectedCount, bibframe -> kafkaTemplate.send(bibframeTopic(tenant), resourceEvent(null, null, bibframe))); } + + if (type.equals(BibframeAuthority.class)) { + setUpTenant(tenant, bibframeAuthoritySearchPath(), postInitAction, asList(records), expectedCount, + bibAuth -> kafkaTemplate.send(bibframeAuthorityTopic(tenant), resourceEvent(null, null, bibAuth))); + } } @SneakyThrows diff --git a/src/test/java/org/folio/search/utils/TestConstants.java b/src/test/java/org/folio/search/utils/TestConstants.java index dc608ee28..9a6d78146 100644 --- a/src/test/java/org/folio/search/utils/TestConstants.java +++ b/src/test/java/org/folio/search/utils/TestConstants.java @@ -38,6 +38,7 @@ public class TestConstants { public static final String INVENTORY_CLASSIFICATION_TYPE_TOPIC = "inventory.classification-type"; public static final String CONSORTIUM_INSTANCE_TOPIC = "search.consortium.instance"; public static final String BIBFRAME_TOPIC = "search.bibframe"; + public static final String BIBFRAME_AUTHORITY_TOPIC = "search.bibframe-authorities"; public static final String CAMPUS_TOPIC = "inventory.campus"; public static final String LOCAL_CN_TYPE = "6fd29f52-5c9c-44d0-b529-e9c5eb3a0aba"; @@ -125,6 +126,10 @@ public static String bibframeTopic(String tenantId) { return getTopicName(tenantId, BIBFRAME_TOPIC); } + public static String bibframeAuthorityTopic(String tenantId) { + return getTopicName(tenantId, BIBFRAME_AUTHORITY_TOPIC); + } + public static String inventoryCampusTopic(String tenantId) { return getTopicName(tenantId, CAMPUS_TOPIC); } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index e04acca15..552bf48bc 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -113,6 +113,9 @@ folio: - name: search.bibframe numPartitions: 1 replicationFactor: 1 + - name: search.bibframe-authorities + numPartitions: 1 + replicationFactor: 1 - name: inventory.campus numPartitions: 1 replicationFactor: 1 @@ -149,6 +152,10 @@ folio: concurrency: ${KAFKA_BIBFRAME_CONCURRENCY:1} topic-pattern: ${KAFKA_BIBFRAME_CONSUMER_PATTERN:(${folio.environment}\.)(.*\.)search\.bibframe} group-id: ${folio.environment}-mod-search-bibframe-group + bibframe-authorities: + concurrency: ${KAFKA_BIBFRAME_CONCURRENCY:1} + topic-pattern: (${folio.environment}\.)(.*\.)search\.bibframe-authorities + group-id: ${folio.environment}-mod-search-bibframe-authorities-group okapiUrl: ${okapi.url} logging: request: diff --git a/src/test/resources/samples/bibframe/authority_concept.json b/src/test/resources/samples/bibframe/authority_concept.json new file mode 100644 index 000000000..04205f469 --- /dev/null +++ b/src/test/resources/samples/bibframe/authority_concept.json @@ -0,0 +1,15 @@ +{ + "id": "123456654321", + "label": "concept-label", + "type": "CONCEPT", + "identifiers": [ + { + "value": "sh 0123456789", + "type": "LCCN" + }, + { + "value": "sh 9876543210", + "type": "LCCN" + } + ] +} diff --git a/src/test/resources/samples/bibframe/authority_person.json b/src/test/resources/samples/bibframe/authority_person.json new file mode 100644 index 000000000..ceb3e408a --- /dev/null +++ b/src/test/resources/samples/bibframe/authority_person.json @@ -0,0 +1,11 @@ +{ + "id": "123456654322", + "label": "person-label", + "type": "PERSON", + "identifiers": [ + { + "value": "n 0123456789", + "type": "LCCN" + } + ] +}