diff --git a/.jpb/jpb-settings.xml b/.jpb/jpb-settings.xml
new file mode 100644
index 000000000..935cf0d5a
--- /dev/null
+++ b/.jpb/jpb-settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 9b4697e40..42bc1876b 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -235,6 +235,36 @@
}
]
},
+ {
+ "id": "browse-config",
+ "version": "1.0",
+ "handlers": [
+ {
+ "methods": [
+ "GET"
+ ],
+ "pathPattern": "/browse/config/{browseType}",
+ "permissionsRequired": [
+ "browse.config.collection.get"
+ ],
+ "modulePermissions": [
+ "user-tenants.collection.get"
+ ]
+ },
+ {
+ "methods": [
+ "PUT"
+ ],
+ "pathPattern": "/browse/config/{browseType}/{browseConfigId}",
+ "permissionsRequired": [
+ "browse.config.item.put"
+ ],
+ "modulePermissions": [
+ "user-tenants.collection.get"
+ ]
+ }
+ ]
+ },
{
"id": "search-config",
"version": "0.2",
@@ -531,6 +561,16 @@
"permissionName": "search.config.features.item.delete",
"displayName": "Search - removes feature configuration",
"description": "Removes feature configuration"
+ },
+ {
+ "permissionName": "browse.config.collection.get",
+ "displayName": "Browse - returns configurations for browse type",
+ "description": "Returns configuration for browse type"
+ },
+ {
+ "permissionName": "browse.config.item.put",
+ "displayName": "Browse - updates configuration entry for browse type",
+ "description": "Updates configuration entry for browse type"
}
],
"launchDescriptor": {
diff --git a/src/main/java/org/folio/search/configuration/WebConfig.java b/src/main/java/org/folio/search/configuration/WebConfig.java
index 01a1ca022..6fb656fb3 100644
--- a/src/main/java/org/folio/search/configuration/WebConfig.java
+++ b/src/main/java/org/folio/search/configuration/WebConfig.java
@@ -1,5 +1,7 @@
package org.folio.search.configuration;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.domain.dto.BrowseType;
import org.folio.search.domain.dto.CallNumberType;
import org.folio.search.domain.dto.RecordType;
import org.springframework.context.annotation.Configuration;
@@ -14,6 +16,8 @@ public class WebConfig implements WebMvcConfigurer {
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToRecordTypeEnumConverter());
registry.addConverter(new StringToCallNumberTypeEnumConverter());
+ registry.addConverter(new StringToBrowseTypeConverter());
+ registry.addConverter(new StringToBrowseOptionTypeConverter());
}
private static final class StringToRecordTypeEnumConverter implements Converter {
@@ -29,4 +33,18 @@ public CallNumberType convert(String source) {
return CallNumberType.valueOf(source.toUpperCase());
}
}
+
+ private static final class StringToBrowseTypeConverter implements Converter {
+ @Override
+ public BrowseType convert(String source) {
+ return BrowseType.fromValue(source.toLowerCase());
+ }
+ }
+
+ private static final class StringToBrowseOptionTypeConverter implements Converter {
+ @Override
+ public BrowseOptionType convert(String source) {
+ return BrowseOptionType.fromValue(source.toLowerCase());
+ }
+ }
}
diff --git a/src/main/java/org/folio/search/controller/ConfigController.java b/src/main/java/org/folio/search/controller/ConfigController.java
index ec742a2b3..9e32b5d39 100644
--- a/src/main/java/org/folio/search/controller/ConfigController.java
+++ b/src/main/java/org/folio/search/controller/ConfigController.java
@@ -6,12 +6,17 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
+import org.folio.search.domain.dto.BrowseConfig;
+import org.folio.search.domain.dto.BrowseConfigCollection;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.domain.dto.BrowseType;
import org.folio.search.domain.dto.FeatureConfig;
import org.folio.search.domain.dto.FeatureConfigs;
import org.folio.search.domain.dto.LanguageConfig;
import org.folio.search.domain.dto.LanguageConfigs;
import org.folio.search.domain.dto.TenantConfiguredFeature;
import org.folio.search.rest.resource.ConfigApi;
+import org.folio.search.service.consortium.BrowseConfigServiceDecorator;
import org.folio.search.service.consortium.FeatureConfigServiceDecorator;
import org.folio.search.service.consortium.LanguageConfigServiceDecorator;
import org.springframework.http.ResponseEntity;
@@ -28,6 +33,7 @@ public class ConfigController implements ConfigApi {
private final LanguageConfigServiceDecorator languageConfigService;
private final FeatureConfigServiceDecorator featureConfigService;
+ private final BrowseConfigServiceDecorator browseConfigService;
@Override
public ResponseEntity createLanguageConfig(@Valid LanguageConfig languageConfig) {
@@ -36,8 +42,9 @@ public ResponseEntity createLanguageConfig(@Valid LanguageConfig
}
@Override
- public ResponseEntity updateLanguageConfig(String code, LanguageConfig languageConfig) {
- return ok(languageConfigService.update(code, languageConfig));
+ public ResponseEntity deleteFeatureConfigurationById(String feature) {
+ featureConfigService.delete(TenantConfiguredFeature.fromValue(feature));
+ return noContent().build();
}
@Override
@@ -47,14 +54,26 @@ public ResponseEntity deleteLanguageConfig(String code) {
return noContent().build();
}
+ @Override
+ public ResponseEntity getAllFeatures() {
+ return ok(featureConfigService.getAll());
+ }
+
@Override
public ResponseEntity getAllLanguageConfigs() {
return ok(languageConfigService.getAll());
}
@Override
- public ResponseEntity getAllFeatures() {
- return ok(featureConfigService.getAll());
+ public ResponseEntity getBrowseConfigs(BrowseType browseType) {
+ return ResponseEntity.ok(browseConfigService.getConfigs(browseType));
+ }
+
+ @Override
+ public ResponseEntity putBrowseConfig(BrowseType browseType, BrowseOptionType browseConfigId,
+ BrowseConfig browseConfig) {
+ browseConfigService.upsertConfig(browseType, browseConfigId, browseConfig);
+ return ResponseEntity.ok().build();
}
@Override
@@ -68,8 +87,8 @@ public ResponseEntity updateFeatureConfiguration(String feature,
}
@Override
- public ResponseEntity deleteFeatureConfigurationById(String feature) {
- featureConfigService.delete(TenantConfiguredFeature.fromValue(feature));
- return noContent().build();
+ public ResponseEntity updateLanguageConfig(String code, LanguageConfig languageConfig) {
+ return ok(languageConfigService.update(code, languageConfig));
}
+
}
diff --git a/src/main/java/org/folio/search/converter/BrowseConfigMapper.java b/src/main/java/org/folio/search/converter/BrowseConfigMapper.java
new file mode 100644
index 000000000..22b710ae2
--- /dev/null
+++ b/src/main/java/org/folio/search/converter/BrowseConfigMapper.java
@@ -0,0 +1,35 @@
+package org.folio.search.converter;
+
+import java.util.List;
+import org.folio.search.domain.dto.BrowseConfig;
+import org.folio.search.domain.dto.BrowseConfigCollection;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.domain.dto.BrowseType;
+import org.folio.search.domain.dto.ShelvingOrderAlgorithmType;
+import org.folio.search.model.config.BrowseConfigEntity;
+import org.folio.search.model.config.BrowseConfigId;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring",
+ imports = {BrowseOptionType.class, BrowseConfigId.class, ShelvingOrderAlgorithmType.class})
+public interface BrowseConfigMapper {
+
+ @Mapping(target = "configId", expression = "java(new BrowseConfigId(type.getValue(), config.getId().getValue()))")
+ @Mapping(target = "shelvingAlgorithm", expression = "java(config.getShelvingAlgorithm().getValue())")
+ BrowseConfigEntity convert(BrowseType type, BrowseConfig config);
+
+ @Mapping(target = "id", expression = "java(BrowseOptionType.fromValue(source.getConfigId().getBrowseOptionType()))")
+ @Mapping(target = "shelvingAlgorithm",
+ expression = "java(ShelvingOrderAlgorithmType.fromValue(source.getShelvingAlgorithm()))")
+ BrowseConfig convert(BrowseConfigEntity source);
+
+ default BrowseConfigCollection convert(List entities) {
+ return map(new BrowseConfigWrapper(entities));
+ }
+
+ @Mapping(target = "totalRecords", expression = "java(source.configs().size())")
+ BrowseConfigCollection map(BrowseConfigWrapper source);
+
+ record BrowseConfigWrapper(List configs) { }
+}
diff --git a/src/main/java/org/folio/search/model/config/BrowseConfigEntity.java b/src/main/java/org/folio/search/model/config/BrowseConfigEntity.java
new file mode 100644
index 000000000..646ccbf65
--- /dev/null
+++ b/src/main/java/org/folio/search/model/config/BrowseConfigEntity.java
@@ -0,0 +1,60 @@
+package org.folio.search.model.config;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Convert;
+import jakarta.persistence.EmbeddedId;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.hibernate.proxy.HibernateProxy;
+
+@Getter
+@Setter
+@ToString
+@RequiredArgsConstructor
+@Entity
+@Table(name = "browse_config")
+public class BrowseConfigEntity {
+
+ @EmbeddedId
+ private BrowseConfigId configId;
+
+ @Column(name = "shelving_algorithm", nullable = false)
+ private String shelvingAlgorithm;
+
+ @Convert(converter = StringListConverter.class)
+ @Column(name = "type_ids", nullable = false)
+ private List typeIds = new ArrayList<>();
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(configId);
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null) {
+ return false;
+ }
+ Class> effectiveClass = o instanceof HibernateProxy
+ ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass()
+ : o.getClass();
+ Class> thisEffectiveClass = this instanceof HibernateProxy
+ ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass()
+ : this.getClass();
+ if (thisEffectiveClass != effectiveClass) {
+ return false;
+ }
+ BrowseConfigEntity that = (BrowseConfigEntity) o;
+ return getConfigId() != null && Objects.equals(getConfigId(), that.getConfigId());
+ }
+}
diff --git a/src/main/java/org/folio/search/model/config/BrowseConfigId.java b/src/main/java/org/folio/search/model/config/BrowseConfigId.java
new file mode 100644
index 000000000..8336c8042
--- /dev/null
+++ b/src/main/java/org/folio/search/model/config/BrowseConfigId.java
@@ -0,0 +1,48 @@
+package org.folio.search.model.config;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import java.util.Objects;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.proxy.HibernateProxy;
+
+@Getter
+@Embeddable
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrowseConfigId {
+
+ @Column(name = "browse_type")
+ private String browseType;
+ @Column(name = "browse_option_type")
+ private String browseOptionType;
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(browseType, browseOptionType);
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null) {
+ return false;
+ }
+ Class> effectiveClass = o instanceof HibernateProxy
+ ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass()
+ : o.getClass();
+ Class> thisEffectiveClass = this instanceof HibernateProxy
+ ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass()
+ : this.getClass();
+ if (thisEffectiveClass != effectiveClass) {
+ return false;
+ }
+ BrowseConfigId that = (BrowseConfigId) o;
+ return browseType != null && Objects.equals(browseType, that.browseType)
+ && browseOptionType != null && Objects.equals(browseOptionType, that.browseOptionType);
+ }
+}
diff --git a/src/main/java/org/folio/search/model/config/StringListConverter.java b/src/main/java/org/folio/search/model/config/StringListConverter.java
new file mode 100644
index 000000000..d7d2ce011
--- /dev/null
+++ b/src/main/java/org/folio/search/model/config/StringListConverter.java
@@ -0,0 +1,23 @@
+package org.folio.search.model.config;
+
+import static java.util.Collections.emptyList;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+import java.util.Arrays;
+import java.util.List;
+
+@Converter
+public class StringListConverter implements AttributeConverter, String> {
+ private static final String SPLIT_CHAR = ";";
+
+ @Override
+ public String convertToDatabaseColumn(List stringList) {
+ return stringList != null ? String.join(SPLIT_CHAR, stringList) : "";
+ }
+
+ @Override
+ public List convertToEntityAttribute(String string) {
+ return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : emptyList();
+ }
+}
diff --git a/src/main/java/org/folio/search/repository/BrowseConfigEntityRepository.java b/src/main/java/org/folio/search/repository/BrowseConfigEntityRepository.java
new file mode 100644
index 000000000..fb38df14a
--- /dev/null
+++ b/src/main/java/org/folio/search/repository/BrowseConfigEntityRepository.java
@@ -0,0 +1,11 @@
+package org.folio.search.repository;
+
+import java.util.List;
+import org.folio.search.model.config.BrowseConfigEntity;
+import org.folio.search.model.config.BrowseConfigId;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface BrowseConfigEntityRepository extends JpaRepository {
+
+ List findByConfigId_BrowseType(String browseType);
+}
diff --git a/src/main/java/org/folio/search/service/config/BrowseConfigService.java b/src/main/java/org/folio/search/service/config/BrowseConfigService.java
new file mode 100644
index 000000000..7317bc6d5
--- /dev/null
+++ b/src/main/java/org/folio/search/service/config/BrowseConfigService.java
@@ -0,0 +1,35 @@
+package org.folio.search.service.config;
+
+import lombok.RequiredArgsConstructor;
+import org.folio.search.converter.BrowseConfigMapper;
+import org.folio.search.domain.dto.BrowseConfig;
+import org.folio.search.domain.dto.BrowseConfigCollection;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.domain.dto.BrowseType;
+import org.folio.search.exception.RequestValidationException;
+import org.folio.search.repository.BrowseConfigEntityRepository;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class BrowseConfigService {
+
+ private static final String BODY_VALIDATION_MSG = "Body doesn't match path parameter: %s";
+
+ private final BrowseConfigEntityRepository repository;
+ private final BrowseConfigMapper mapper;
+
+ public BrowseConfigCollection getConfigs(BrowseType type) {
+ return mapper.convert(repository.findByConfigId_BrowseType(type.getValue()));
+ }
+
+ public void upsertConfig(BrowseType type, BrowseOptionType configId, BrowseConfig config) {
+ if (configId != null && configId != config.getId()) {
+ throw new RequestValidationException(
+ BODY_VALIDATION_MSG.formatted(configId.getValue()), "id", config.getId().toString());
+ }
+
+ var configEntity = mapper.convert(type, config);
+ repository.save(configEntity);
+ }
+}
diff --git a/src/main/java/org/folio/search/service/consortium/BrowseConfigServiceDecorator.java b/src/main/java/org/folio/search/service/consortium/BrowseConfigServiceDecorator.java
new file mode 100644
index 000000000..59e3f657d
--- /dev/null
+++ b/src/main/java/org/folio/search/service/consortium/BrowseConfigServiceDecorator.java
@@ -0,0 +1,26 @@
+package org.folio.search.service.consortium;
+
+import lombok.RequiredArgsConstructor;
+import org.folio.search.domain.dto.BrowseConfig;
+import org.folio.search.domain.dto.BrowseConfigCollection;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.domain.dto.BrowseType;
+import org.folio.search.service.config.BrowseConfigService;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class BrowseConfigServiceDecorator {
+
+ private final ConsortiumTenantExecutor consortiumTenantExecutor;
+ private final BrowseConfigService browseConfigService;
+
+ public BrowseConfigCollection getConfigs(BrowseType type) {
+ return consortiumTenantExecutor.execute(() -> browseConfigService.getConfigs(type));
+ }
+
+ public void upsertConfig(BrowseType type, BrowseOptionType configId,
+ BrowseConfig config) {
+ consortiumTenantExecutor.run(() -> browseConfigService.upsertConfig(type, configId, config));
+ }
+}
diff --git a/src/main/resources/changelog/changelog-master.xml b/src/main/resources/changelog/changelog-master.xml
index e8e346130..b0b22c9ba 100644
--- a/src/main/resources/changelog/changelog-master.xml
+++ b/src/main/resources/changelog/changelog-master.xml
@@ -8,4 +8,5 @@
+
diff --git a/src/main/resources/changelog/changes/v3.2/create_browse_config_table.xml b/src/main/resources/changelog/changes/v3.2/create_browse_config_table.xml
new file mode 100644
index 000000000..382f980ee
--- /dev/null
+++ b/src/main/resources/changelog/changes/v3.2/create_browse_config_table.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+ Create browse_config table
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Populate browse_config table with default values
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/swagger.api/mod-search.yaml b/src/main/resources/swagger.api/mod-search.yaml
index fa45060ad..7d8156278 100644
--- a/src/main/resources/swagger.api/mod-search.yaml
+++ b/src/main/resources/swagger.api/mod-search.yaml
@@ -569,6 +569,44 @@ paths:
'404':
description: No feature configuration is found by id
+ /browse/config/{browseType}:
+ get:
+ operationId: getBrowseConfigs
+ description: Get all configurations for browse type
+ tags:
+ - config
+ parameters:
+ - $ref: '#/components/parameters/browse-type'
+ responses:
+ '200':
+ description: All browse configurations for type
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/browseConfigCollection'
+
+ /browse/config/{browseType}/{browseConfigId}:
+ put:
+ operationId: putBrowseConfig
+ description: Update configuration for browse type
+ tags:
+ - config
+ parameters:
+ - $ref: '#/components/parameters/browse-type'
+ - $ref: '#/components/parameters/browse-config-id'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/browseConfig'
+ responses:
+ '200':
+ description: Browse configuration has been added/updated.
+ '400':
+ $ref: '#/components/responses/badRequestResponse'
+ '422':
+ $ref: '#/components/responses/unprocessableEntityResponse'
+
components:
schemas:
instance:
@@ -613,6 +651,46 @@ components:
CallNumberType:
enum: [ lc, dewey, nlm, sudoc, other, local ]
type: string
+ browseType:
+ type: string
+ enum:
+ - instance-classification
+ browseOptionType:
+ type: string
+ enum:
+ - all
+ - lc
+ - dewey
+ shelvingOrderAlgorithmType:
+ type: string
+ enum:
+ - lc
+ - dewey
+ - default
+ browseConfig:
+ type: object
+ properties:
+ id:
+ description: Option ID
+ $ref: "#/components/schemas/browseOptionType"
+ shelvingAlgorithm:
+ description: Defines shelving order algorithm
+ $ref: "#/components/schemas/shelvingOrderAlgorithmType"
+ typeIds:
+ description: Type IDs that should be used by the option
+ type: array
+ items:
+ description: Classification type ID
+ type: string
+ browseConfigCollection:
+ type: object
+ properties:
+ configs:
+ type: array
+ items:
+ $ref: '#/components/schemas/browseConfig'
+ totalRecords:
+ type: integer
responses:
unprocessableEntityResponse:
@@ -750,4 +828,17 @@ components:
schema:
type: boolean
default: true
-
+ browse-config-id:
+ name: browseConfigId
+ in: path
+ required: true
+ description: 'ID of browse config'
+ schema:
+ $ref: '#/components/schemas/browseOptionType'
+ browse-type:
+ name: browseType
+ in: path
+ required: true
+ description: 'Browse feature type'
+ schema:
+ $ref: '#/components/schemas/browseType'
diff --git a/src/test/java/org/folio/search/controller/ConfigControllerTest.java b/src/test/java/org/folio/search/controller/ConfigControllerTest.java
index 1d71e1fc2..d33b3cee2 100644
--- a/src/test/java/org/folio/search/controller/ConfigControllerTest.java
+++ b/src/test/java/org/folio/search/controller/ConfigControllerTest.java
@@ -1,10 +1,12 @@
package org.folio.search.controller;
+import static org.folio.search.domain.dto.BrowseType.INSTANCE_CLASSIFICATION;
import static org.folio.search.domain.dto.TenantConfiguredFeature.SEARCH_ALL_FIELDS;
import static org.folio.search.utils.TestConstants.TENANT_ID;
import static org.folio.search.utils.TestUtils.asJsonString;
import static org.folio.search.utils.TestUtils.languageConfig;
import static org.folio.search.utils.TestUtils.mapOf;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.doNothing;
@@ -19,22 +21,29 @@
import jakarta.persistence.EntityNotFoundException;
import java.util.List;
+import org.folio.search.domain.dto.BrowseConfig;
+import org.folio.search.domain.dto.BrowseConfigCollection;
+import org.folio.search.domain.dto.BrowseOptionType;
import org.folio.search.domain.dto.FeatureConfig;
import org.folio.search.domain.dto.FeatureConfigs;
import org.folio.search.domain.dto.LanguageConfigs;
+import org.folio.search.domain.dto.ShelvingOrderAlgorithmType;
import org.folio.search.exception.RequestValidationException;
import org.folio.search.exception.ValidationException;
+import org.folio.search.service.consortium.BrowseConfigServiceDecorator;
import org.folio.search.service.consortium.FeatureConfigServiceDecorator;
import org.folio.search.service.consortium.LanguageConfigServiceDecorator;
import org.folio.search.support.base.ApiEndpoints;
import org.folio.spring.integration.XOkapiHeaders;
import org.folio.spring.testing.type.UnitTest;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@UnitTest
@Import(ApiExceptionHandler.class)
@@ -47,6 +56,8 @@ class ConfigControllerTest {
private LanguageConfigServiceDecorator languageConfigService;
@MockBean
private FeatureConfigServiceDecorator featureConfigService;
+ @MockBean
+ private BrowseConfigServiceDecorator browseConfigService;
@Test
void createLanguageConfig_positive() throws Exception {
@@ -263,4 +274,37 @@ void deleteFeatureConfigurationById_positive() throws Exception {
mockMvc.perform(request).andExpect(status().isNoContent());
}
+
+ @Test
+ void getBrowseConfigs_positive() throws Exception {
+ var config = new BrowseConfig().id(BrowseOptionType.LC).shelvingAlgorithm(ShelvingOrderAlgorithmType.LC)
+ .typeIds(List.of("t1", "t2"));
+ when(browseConfigService.getConfigs(INSTANCE_CLASSIFICATION))
+ .thenReturn(new BrowseConfigCollection().addConfigsItem(config).totalRecords(1));
+
+ var request = get(ApiEndpoints.browseConfigPath(INSTANCE_CLASSIFICATION))
+ .header(XOkapiHeaders.TENANT, TENANT_ID)
+ .contentType(APPLICATION_JSON);
+
+ mockMvc.perform(request)
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.totalRecords", is(1)))
+ .andExpect(jsonPath("$.configs[0].id", is(BrowseOptionType.LC.getValue())))
+ .andExpect(jsonPath("$.configs[0].shelvingAlgorithm", is(ShelvingOrderAlgorithmType.LC.getValue())))
+ .andExpect(jsonPath("$.configs[0].typeIds[*]", containsInAnyOrder(config.getTypeIds().toArray())));
+ }
+
+ @Test
+ void putBrowseConfig_positive() throws Exception {
+ var config = new BrowseConfig().id(BrowseOptionType.LC).shelvingAlgorithm(ShelvingOrderAlgorithmType.LC)
+ .typeIds(List.of("t1", "t2"));
+ doNothing().when(browseConfigService).upsertConfig(INSTANCE_CLASSIFICATION, BrowseOptionType.LC, config);
+
+ var request = put(ApiEndpoints.browseConfigPath(INSTANCE_CLASSIFICATION, BrowseOptionType.LC))
+ .header(XOkapiHeaders.TENANT, TENANT_ID)
+ .contentType(APPLICATION_JSON);
+
+ mockMvc.perform(request).andExpect(status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string(Matchers.emptyOrNullString()));
+ }
}
diff --git a/src/test/java/org/folio/search/controller/ConfigIT.java b/src/test/java/org/folio/search/controller/ConfigIT.java
index 11dd1a89e..5f7253741 100644
--- a/src/test/java/org/folio/search/controller/ConfigIT.java
+++ b/src/test/java/org/folio/search/controller/ConfigIT.java
@@ -1,5 +1,6 @@
package org.folio.search.controller;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.folio.search.domain.dto.TenantConfiguredFeature.SEARCH_ALL_FIELDS;
import static org.folio.search.sample.SampleInstances.getSemanticWebAsMap;
@@ -11,9 +12,6 @@
import static org.folio.search.utils.TestUtils.parseResponse;
import static org.folio.search.utils.TestUtils.randomId;
import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.opensearch.index.query.QueryBuilders.matchQuery;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -22,10 +20,15 @@
import java.util.List;
import java.util.Map;
import lombok.SneakyThrows;
+import org.folio.search.domain.dto.BrowseConfig;
+import org.folio.search.domain.dto.BrowseConfigCollection;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.domain.dto.BrowseType;
import org.folio.search.domain.dto.FeatureConfig;
import org.folio.search.domain.dto.Instance;
import org.folio.search.domain.dto.LanguageConfig;
import org.folio.search.domain.dto.LanguageConfigs;
+import org.folio.search.domain.dto.ShelvingOrderAlgorithmType;
import org.folio.search.support.base.ApiEndpoints;
import org.folio.search.support.base.BaseIntegrationTest;
import org.folio.search.utils.TestUtils;
@@ -149,11 +152,11 @@ void shouldUseConfiguredLanguagesDuringMapping() {
final var indexedInstance = getIndexedInstanceById(newInstance.getId());
- assertThat((Map) getMapValueByPath("title", indexedInstance), aMapWithSize(3));
- assertThat(getMapValueByPath("title.eng", indexedInstance), is(newInstance.getTitle()));
- assertThat(getMapValueByPath("title.rus", indexedInstance), is(newInstance.getTitle()));
- assertThat(getMapValueByPath("title.src", indexedInstance), is(newInstance.getTitle()));
- assertThat(getMapValueByPath("title.fre", indexedInstance), nullValue());
+ assertThat((Map) getMapValueByPath("title", indexedInstance)).hasSize(3);
+ assertThat(getMapValueByPath("title.eng", indexedInstance)).isEqualTo(newInstance.getTitle());
+ assertThat(getMapValueByPath("title.rus", indexedInstance)).isEqualTo(newInstance.getTitle());
+ assertThat(getMapValueByPath("title.src", indexedInstance)).isEqualTo(newInstance.getTitle());
+ assertThat(getMapValueByPath("title.fre", indexedInstance)).isNull();
}
@Test
@@ -215,6 +218,32 @@ void deleteUnknownFeature_notExists() throws Exception {
.andExpect(jsonPath("$.errors[0].message", is("Feature configuration not found for id: search.all.fields")));
}
+ @Test
+ void getBrowseConfigs_positive() throws Exception {
+ doGet(ApiEndpoints.browseConfigPath(BrowseType.INSTANCE_CLASSIFICATION))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.totalRecords", is(3)));
+ }
+
+ @Test
+ void putBrowseConfigs_positive() throws Exception {
+ var config = new BrowseConfig().id(BrowseOptionType.LC)
+ .shelvingAlgorithm(ShelvingOrderAlgorithmType.DEFAULT)
+ .addTypeIdsItem("id1").addTypeIdsItem("id2");
+
+ doPut(ApiEndpoints.browseConfigPath(BrowseType.INSTANCE_CLASSIFICATION, BrowseOptionType.LC), config)
+ .andExpect(status().isOk());
+
+ var result = doGet(ApiEndpoints.browseConfigPath(BrowseType.INSTANCE_CLASSIFICATION))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.totalRecords", is(3)));
+
+ var configCollection = parseResponse(result, BrowseConfigCollection.class);
+ assertThat(configCollection.getConfigs())
+ .hasSize(3)
+ .contains(config);
+ }
+
@SneakyThrows
private Map getIndexedInstanceById(String id) {
final var searchRequest = new SearchRequest()
@@ -222,7 +251,7 @@ private Map getIndexedInstanceById(String id) {
.indices(getIndexName(INSTANCE_RESOURCE, TENANT_ID));
await().until(() -> elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT)
- .getHits().getTotalHits().value > 0);
+ .getHits().getTotalHits().value > 0);
return elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT).getHits()
.getAt(0).getSourceAsMap();
diff --git a/src/test/java/org/folio/search/service/config/BrowseConfigServiceTest.java b/src/test/java/org/folio/search/service/config/BrowseConfigServiceTest.java
new file mode 100644
index 000000000..082f6a02d
--- /dev/null
+++ b/src/test/java/org/folio/search/service/config/BrowseConfigServiceTest.java
@@ -0,0 +1,93 @@
+package org.folio.search.service.config;
+
+import static org.folio.search.domain.dto.BrowseOptionType.ALL;
+import static org.folio.search.domain.dto.BrowseOptionType.LC;
+import static org.folio.search.domain.dto.BrowseType.INSTANCE_CLASSIFICATION;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+
+import java.util.List;
+import org.folio.search.converter.BrowseConfigMapper;
+import org.folio.search.domain.dto.BrowseConfig;
+import org.folio.search.domain.dto.BrowseConfigCollection;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.domain.dto.BrowseType;
+import org.folio.search.domain.dto.ShelvingOrderAlgorithmType;
+import org.folio.search.exception.RequestValidationException;
+import org.folio.search.model.config.BrowseConfigEntity;
+import org.folio.search.model.config.BrowseConfigId;
+import org.folio.search.repository.BrowseConfigEntityRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class BrowseConfigServiceTest {
+
+ @Mock
+ private BrowseConfigEntityRepository repository;
+
+ @Mock
+ private BrowseConfigMapper mapper;
+
+ @InjectMocks
+ private BrowseConfigService service;
+
+ private BrowseType type;
+ private BrowseOptionType configId;
+ private BrowseConfig config;
+
+ @BeforeEach
+ void setUp() {
+ type = INSTANCE_CLASSIFICATION;
+ configId = LC;
+ config = new BrowseConfig().id(LC).shelvingAlgorithm(ShelvingOrderAlgorithmType.LC);
+ }
+
+ @Test
+ void shouldGetConfigs() {
+ List entities = List.of(getEntity());
+ BrowseConfigCollection configs = new BrowseConfigCollection().addConfigsItem(config);
+ given(repository.findByConfigId_BrowseType(type.getValue())).willReturn(entities);
+ given(mapper.convert(entities)).willReturn(configs);
+
+ var result = service.getConfigs(type);
+
+ assertEquals(configs, result);
+ verify(repository).findByConfigId_BrowseType(type.getValue());
+ }
+
+ @Test
+ void shouldUpsertConfigWhenConfigIdMatches() {
+ var entity = getEntity();
+ given(mapper.convert(type, config)).willReturn(entity);
+
+ assertDoesNotThrow(() -> service.upsertConfig(type, configId, config));
+ verify(repository).save(entity);
+ }
+
+ @Test
+ void shouldThrowExceptionWhenConfigIdNotMatches() {
+ config.setId(ALL);
+
+ var exception = assertThrows(RequestValidationException.class, () -> service.upsertConfig(type, configId, config));
+
+ String expectedMessage = String.format("Body doesn't match path parameter: %s", configId.getValue());
+ assertTrue(exception.getMessage().contains(expectedMessage));
+ }
+
+ private static BrowseConfigEntity getEntity() {
+ var configEntity = new BrowseConfigEntity();
+ configEntity.setConfigId(new BrowseConfigId(INSTANCE_CLASSIFICATION.getValue(), LC.getValue()));
+ configEntity.setShelvingAlgorithm(ShelvingOrderAlgorithmType.LC.getValue());
+ configEntity.setTypeIds(List.of("e1", "e2"));
+ return configEntity;
+ }
+}
diff --git a/src/test/java/org/folio/search/service/consortium/BrowseConfigServiceDecoratorTest.java b/src/test/java/org/folio/search/service/consortium/BrowseConfigServiceDecoratorTest.java
new file mode 100644
index 000000000..339bce9a4
--- /dev/null
+++ b/src/test/java/org/folio/search/service/consortium/BrowseConfigServiceDecoratorTest.java
@@ -0,0 +1,53 @@
+package org.folio.search.service.consortium;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.folio.search.domain.dto.BrowseType.INSTANCE_CLASSIFICATION;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.folio.search.domain.dto.BrowseConfig;
+import org.folio.search.domain.dto.BrowseConfigCollection;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.service.config.BrowseConfigService;
+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.junit.jupiter.MockitoExtension;
+
+@UnitTest
+@ExtendWith(MockitoExtension.class)
+class BrowseConfigServiceDecoratorTest extends DecoratorBaseTest {
+
+ private @Mock ConsortiumTenantExecutor consortiumTenantExecutor;
+ private @Mock BrowseConfigService service;
+ private @InjectMocks BrowseConfigServiceDecorator decorator;
+
+ @Test
+ void getConfigs() {
+ var expected = new BrowseConfigCollection();
+ when(service.getConfigs(INSTANCE_CLASSIFICATION)).thenReturn(expected);
+ mockExecutor(consortiumTenantExecutor);
+
+ var actual = decorator.getConfigs(INSTANCE_CLASSIFICATION);
+
+ assertThat(actual).isEqualTo(expected);
+ verify(service).getConfigs(INSTANCE_CLASSIFICATION);
+ verify(consortiumTenantExecutor).execute(any());
+ }
+
+ @Test
+ void upsertConfig() {
+ var config = new BrowseConfig();
+ doNothing().when(service).upsertConfig(INSTANCE_CLASSIFICATION, BrowseOptionType.LC, config);
+ mockExecutorRun(consortiumTenantExecutor);
+
+ decorator.upsertConfig(INSTANCE_CLASSIFICATION, BrowseOptionType.LC, config);
+
+ verify(service).upsertConfig(INSTANCE_CLASSIFICATION, BrowseOptionType.LC, config);
+ verify(consortiumTenantExecutor).run(any());
+ }
+}
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 f17351b2f..fbf6b50d7 100644
--- a/src/test/java/org/folio/search/support/base/ApiEndpoints.java
+++ b/src/test/java/org/folio/search/support/base/ApiEndpoints.java
@@ -2,6 +2,8 @@
import lombok.experimental.UtilityClass;
import org.folio.cql2pgjson.model.CqlSort;
+import org.folio.search.domain.dto.BrowseOptionType;
+import org.folio.search.domain.dto.BrowseType;
import org.folio.search.domain.dto.RecordType;
import org.folio.search.domain.dto.TenantConfiguredFeature;
@@ -53,6 +55,14 @@ public static String featureConfigPath(TenantConfiguredFeature feature) {
return featureConfigPath() + "/" + feature.getValue();
}
+ public static String browseConfigPath(BrowseType type) {
+ return "/browse/config/" + type.getValue();
+ }
+
+ public static String browseConfigPath(BrowseType type, BrowseOptionType optionType) {
+ return "/browse/config/" + type.getValue() + "/" + optionType.getValue();
+ }
+
public static String createIndicesPath() {
return "/search/index/indices";
}