diff --git a/NEWS.md b/NEWS.md index c477408b3..dfc016141 100644 --- a/NEWS.md +++ b/NEWS.md @@ -104,6 +104,7 @@ - Create `GET /vocabularies/{name}` API to return a vocabulary used by UI. [MODLD-982](https://folio-org.atlassian.net/browse/MODLD-982) - Include subTitle (OtherTitleInformation) in work profile [MODLD-1009](https://folio-org.atlassian.net/browse/MODLD-1009) - Improve export resource query performance [MODLD-1010](https://folio-org.atlassian.net/browse/MODLD-1010) +- Retrieve FOLIO base-url from `GET /base-url` API [MODLD-804](https://folio-org.atlassian.net/browse/MODLD-804) ## 1.0.4 (04-24-2025) - Work Edit form - Instance read-only section: "Notes about the instance" data is not shown [MODLD-716](https://folio-org.atlassian.net/browse/MODLD-716) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 68f4a3d97..73dd4f8e5 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -42,7 +42,9 @@ "methods": [ "GET" ], "pathPattern": "/linked-data/resource/{id}/rdf", "permissionsRequired": [ "linked-data.resources.rdf.get" ], - "modulePermissions": [] + "modulePermissions": [ + "base-url.item.get" + ] }, { "methods": [ "PUT" ], @@ -242,11 +244,15 @@ }, { "id": "settings", - "version": "1.1" + "version": "1.2" }, { "id": "authority-source-files", "version": "2.2" + }, + { + "id": "base-url", + "version": "1.0" } ], "permissionSets": [ diff --git a/pom.xml b/pom.xml index aeea1b3d6..ed6535c88 100644 --- a/pom.xml +++ b/pom.xml @@ -529,6 +529,7 @@ false false false + ${project.basedir}/src/main/resources/swagger.api/templates true java8 diff --git a/src/main/java/org/folio/linked/data/configuration/HttpClientConfiguration.java b/src/main/java/org/folio/linked/data/configuration/HttpClientConfiguration.java index 666a29294..58a6386a5 100644 --- a/src/main/java/org/folio/linked/data/configuration/HttpClientConfiguration.java +++ b/src/main/java/org/folio/linked/data/configuration/HttpClientConfiguration.java @@ -1,7 +1,6 @@ package org.folio.linked.data.configuration; import org.folio.linked.data.integration.rest.authoritysource.AuthoritySourceFilesClient; -import org.folio.linked.data.integration.rest.configuration.ConfigurationClient; import org.folio.linked.data.integration.rest.search.SearchClient; import org.folio.linked.data.integration.rest.settings.SettingsClient; import org.folio.linked.data.integration.rest.specification.SpecClient; @@ -18,11 +17,6 @@ public AuthoritySourceFilesClient authoritySourceFilesClient(HttpServiceProxyFac return factory.createClient(AuthoritySourceFilesClient.class); } - @Bean - public ConfigurationClient configurationClient(HttpServiceProxyFactory factory) { - return factory.createClient(ConfigurationClient.class); - } - @Bean public SearchClient searchClient(HttpServiceProxyFactory factory) { return factory.createClient(SearchClient.class); diff --git a/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationClient.java b/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationClient.java deleted file mode 100644 index 6e3935856..000000000 --- a/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationClient.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.folio.linked.data.integration.rest.configuration; - -import static org.folio.linked.data.util.Constants.Cache.SETTINGS_ENTRIES; -import static org.folio.linked.data.util.Constants.STANDALONE_PROFILE; - -import org.folio.linked.data.domain.dto.Configurations; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.annotation.Profile; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.service.annotation.GetExchange; -import org.springframework.web.service.annotation.HttpExchange; - -@HttpExchange("configurations") -@Profile("!" + STANDALONE_PROFILE) -public interface ConfigurationClient { - - @SuppressWarnings("java:S7180") - @Cacheable(cacheNames = SETTINGS_ENTRIES, key = "@folioExecutionContext.tenantId + '_' + #code") - @GetExchange("/entries?query=module=={moduleName} and code=={code}") - Configurations lookupConfig(@PathVariable("moduleName") String moduleName, - @PathVariable("code") String code); - -} diff --git a/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationService.java b/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationService.java deleted file mode 100644 index b8fad31ff..000000000 --- a/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationService.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.folio.linked.data.integration.rest.configuration; - -public interface ConfigurationService { - - String getFolioHost(); - -} diff --git a/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationServiceImpl.java b/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationServiceImpl.java deleted file mode 100644 index 4606b7e49..000000000 --- a/src/main/java/org/folio/linked/data/integration/rest/configuration/ConfigurationServiceImpl.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.folio.linked.data.integration.rest.configuration; - -import static org.folio.linked.data.util.Constants.STANDALONE_PROFILE; - -import lombok.RequiredArgsConstructor; -import org.folio.linked.data.domain.dto.Config; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Profile("!" + STANDALONE_PROFILE) -public class ConfigurationServiceImpl implements ConfigurationService { - private static final String MODULE_NAME = "USERSBL"; - private static final String FOLIO_HOST_CONFIG_KEY = "FOLIO_HOST"; - private static final String FOLIO_HOST_DEFAULT = "http://localhost:8081"; - private final ConfigurationClient configurationClient; - - @Override - public String getFolioHost() { - return configurationClient.lookupConfig(MODULE_NAME, FOLIO_HOST_CONFIG_KEY) - .getConfigs() - .stream() - .map(Config::getValue) - .findFirst() - .orElse(FOLIO_HOST_DEFAULT); - } - -} diff --git a/src/main/java/org/folio/linked/data/integration/rest/settings/SettingsClient.java b/src/main/java/org/folio/linked/data/integration/rest/settings/SettingsClient.java index 0c3cfcc44..cb4e38865 100644 --- a/src/main/java/org/folio/linked/data/integration/rest/settings/SettingsClient.java +++ b/src/main/java/org/folio/linked/data/integration/rest/settings/SettingsClient.java @@ -3,6 +3,7 @@ import static org.folio.linked.data.util.Constants.Cache.SETTINGS_ENTRIES; import static org.folio.linked.data.util.Constants.STANDALONE_PROFILE; +import org.folio.linked.data.domain.dto.BaseUrlDto; import org.folio.linked.data.domain.dto.SettingsSearchResponse; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Profile; @@ -11,12 +12,17 @@ import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.HttpExchange; -@HttpExchange("settings") +@HttpExchange @Profile("!" + STANDALONE_PROFILE) public interface SettingsClient { @SuppressWarnings("java:S7180") @Cacheable(cacheNames = SETTINGS_ENTRIES, key = "@folioExecutionContext.tenantId + '_' + #query") - @GetExchange("/entries") + @GetExchange("settings/entries") ResponseEntity getEntries(@RequestParam("query") String query); + + @SuppressWarnings("java:S7180") + @Cacheable(cacheNames = SETTINGS_ENTRIES, key = "@folioExecutionContext.tenantId + '_base-url'") + @GetExchange("base-url") + BaseUrlDto getBaseUrl(); } diff --git a/src/main/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapper.java b/src/main/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapper.java index 3b19e149c..c6f12d5fa 100644 --- a/src/main/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapper.java +++ b/src/main/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapper.java @@ -22,8 +22,6 @@ @SuppressWarnings("java:S6813") public abstract class InstanceIngressMessageMapper { - private static final String LINKED_DATA_ID = "linkedDataId"; - private static final String INSTANCE_ID = "instanceId"; @Autowired protected Ld2MarcMapper ld2MarcMapper; @Autowired @@ -45,9 +43,9 @@ protected String toMarcJson(Resource resource) { @AfterMapping protected void afterMappingPayload(@MappingTarget InstanceIngressPayload payload, Resource resource) { - payload.putAdditionalProperty(LINKED_DATA_ID, resource.getId()); + payload.setLinkedDataId(resource.getId()); ofNullable(resource.getFolioMetadata()) .map(FolioMetadata::getInventoryId) - .ifPresent(invId -> payload.putAdditionalProperty(INSTANCE_ID, invId)); + .ifPresent(payload::setInstanceId); } } diff --git a/src/main/java/org/folio/linked/data/service/rdf/ResourceUrlProvider.java b/src/main/java/org/folio/linked/data/service/rdf/ResourceUrlProvider.java index f85ed354a..8b4ca7c0c 100644 --- a/src/main/java/org/folio/linked/data/service/rdf/ResourceUrlProvider.java +++ b/src/main/java/org/folio/linked/data/service/rdf/ResourceUrlProvider.java @@ -4,7 +4,7 @@ import java.util.function.LongFunction; import lombok.RequiredArgsConstructor; -import org.folio.linked.data.integration.rest.configuration.ConfigurationService; +import org.folio.linked.data.integration.rest.settings.SettingsClient; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -14,12 +14,20 @@ public class ResourceUrlProvider implements LongFunction { private static final String URL_PATTERN = "%s/linked-data-editor/resources/%s"; - private final ConfigurationService configurationService; - + private final SettingsClient settingsClient; @Override public String apply(long id) { - var folioHost = configurationService.getFolioHost(); - return String.format(URL_PATTERN, folioHost, id); + var baseUrlResponse = settingsClient.getBaseUrl(); + var baseUrl = normalizeBaseUrl(baseUrlResponse.getValue()); + return String.format(URL_PATTERN, baseUrl, id); + } + + private String normalizeBaseUrl(String baseUrl) { + var normalized = baseUrl; + while (normalized.endsWith("/")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + return normalized; } } diff --git a/src/main/resources/swagger.api/folio-modules.yaml b/src/main/resources/swagger.api/folio-modules.yaml index 3017131e8..764b3e1f7 100644 --- a/src/main/resources/swagger.api/folio-modules.yaml +++ b/src/main/resources/swagger.api/folio-modules.yaml @@ -32,8 +32,8 @@ components: $ref: folio-modules/srs/sourceRecordDomainEvent.json inventoryInstanceEvent: $ref: folio-modules/inventory/inventoryInstanceEvent.json - configurationsDto: - $ref: folio-modules/configuration/configurations.json + baseUrlDto: + $ref: folio-modules/settings/baseUrl.json importOutputEvent: $ref: folio-modules/ld-import/importOutputEvent.json importResultEvent: diff --git a/src/main/resources/swagger.api/folio-modules/configuration/config.json b/src/main/resources/swagger.api/folio-modules/configuration/config.json deleted file mode 100644 index b345bf86f..000000000 --- a/src/main/resources/swagger.api/folio-modules/configuration/config.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "description": "mod-configuration DTO for a single configuration", - "properties": { - "id": { - "type": "string", - "description": "ID of the configuration record", - "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" - }, - "code": { - "type": "string", - "description": "Configuration code (key)" - }, - "value": { - "type": "string", - "description": "Configuration value" - } - }, - "additionalProperties": false, - "required": [ - "code", - "value" - ] -} diff --git a/src/main/resources/swagger.api/folio-modules/configuration/configurations.json b/src/main/resources/swagger.api/folio-modules/configuration/configurations.json deleted file mode 100644 index 472f733e4..000000000 --- a/src/main/resources/swagger.api/folio-modules/configuration/configurations.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "description": "mod-configuration DTO for multiple configurations", - "properties": { - "configs": { - "description": "configurationData", - "type": "array", - "items": { - "type": "object", - "$ref": "config.json" - } - }, - "totalRecords": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "configs", - "totalRecords" - ] -} diff --git a/src/main/resources/swagger.api/folio-modules/inventory/instanceIngressPayload.json b/src/main/resources/swagger.api/folio-modules/inventory/instanceIngressPayload.json index 3533b307a..0a3a31bd7 100644 --- a/src/main/resources/swagger.api/folio-modules/inventory/instanceIngressPayload.json +++ b/src/main/resources/swagger.api/folio-modules/inventory/instanceIngressPayload.json @@ -15,9 +15,17 @@ "type": "string", "enum": ["FOLIO", "LINKED_DATA", "MARC"], "description": "Source type" + }, + "linkedDataId": { + "type": "integer", + "format": "int64", + "description": "Linked data resource identifier" + }, + "instanceId": { + "type": "string", + "description": "Inventory instance identifier" } }, - "additionalProperties": true, "required": [ "sourceRecordObject", "sourceType" diff --git a/src/main/resources/swagger.api/folio-modules/settings/baseUrl.json b/src/main/resources/swagger.api/folio-modules/settings/baseUrl.json new file mode 100644 index 000000000..c8f5a39d2 --- /dev/null +++ b/src/main/resources/swagger.api/folio-modules/settings/baseUrl.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Base URL response", + "properties": { + "value": { + "type": "string", + "description": "FOLIO front-end URL", + "x-json-property": "baseUrl" + } + }, + "required": [ + "value" + ] +} diff --git a/src/main/resources/swagger.api/folio-modules/srs/record/parsedRecord.json b/src/main/resources/swagger.api/folio-modules/srs/record/parsedRecord.json index 8f5e32b04..2d16b8a36 100644 --- a/src/main/resources/swagger.api/folio-modules/srs/record/parsedRecord.json +++ b/src/main/resources/swagger.api/folio-modules/srs/record/parsedRecord.json @@ -2,7 +2,6 @@ "$schema": "http://json-schema.org/draft-04/schema#", "description": "Parsed Record Schema", "type": "object", - "additionalProperties": false, "properties": { "id": { "description": "UUID", diff --git a/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecord.json b/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecord.json index a6f589034..a5637b82a 100644 --- a/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecord.json +++ b/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecord.json @@ -2,7 +2,6 @@ "$schema": "http://json-schema.org/draft-04/schema#", "description": "Record DTO Schema", "type": "object", - "additionalProperties": false, "properties": { "id": { "description": "UUID", diff --git a/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecordMetadata.json b/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecordMetadata.json index b4d84e596..8ae1e91b9 100644 --- a/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecordMetadata.json +++ b/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecordMetadata.json @@ -33,7 +33,6 @@ "type": "string" } }, - "additionalProperties": false, "required": [ "createdDate" ] diff --git a/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecordType.json b/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecordType.json index 23f20c979..b39a93fb4 100644 --- a/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecordType.json +++ b/src/main/resources/swagger.api/folio-modules/srs/record/sourceRecordType.json @@ -2,7 +2,6 @@ "$schema": "http://json-schema.org/draft-04/schema#", "description": "Record Type Enum", "type": "string", - "additionalProperties": false, "enum": [ "MARC_BIB", "MARC_AUTHORITY", diff --git a/src/main/resources/swagger.api/folio-modules/srs/sourceRecordDomainEvent.json b/src/main/resources/swagger.api/folio-modules/srs/sourceRecordDomainEvent.json index bd14c174f..9e95e90dd 100644 --- a/src/main/resources/swagger.api/folio-modules/srs/sourceRecordDomainEvent.json +++ b/src/main/resources/swagger.api/folio-modules/srs/sourceRecordDomainEvent.json @@ -3,7 +3,6 @@ "description": "Source record domain event data model", "javaType": "org.folio.rest.jaxrs.model.SourceRecordDomainEvent", "type": "object", - "additionalProperties": false, "properties": { "id": { "description": "UUID", diff --git a/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerUpdateWorkIT.java b/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerUpdateWorkIT.java index 668e651c9..2d950ca90 100644 --- a/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerUpdateWorkIT.java +++ b/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerUpdateWorkIT.java @@ -213,7 +213,7 @@ private boolean isExpectedEvent(String eventStr, long linkedDataId) { var eventPayload = event.getEventPayload(); var marc = eventPayload.getSourceRecordObject(); return event.getEventType() == InstanceIngressEvent.EventTypeEnum.UPDATE_INSTANCE - && eventPayload.getAdditionalProperties().get("linkedDataId").equals(linkedDataId) + && eventPayload.getLinkedDataId().equals(linkedDataId) && isExpectedMarc(marc); } diff --git a/src/test/java/org/folio/linked/data/integration/rest/configuration/ConfigurationClientCacheIT.java b/src/test/java/org/folio/linked/data/integration/rest/configuration/ConfigurationClientCacheIT.java deleted file mode 100644 index cc81782dd..000000000 --- a/src/test/java/org/folio/linked/data/integration/rest/configuration/ConfigurationClientCacheIT.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.folio.linked.data.integration.rest.configuration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.folio.linked.data.test.TestUtil.TENANT_ID; -import static org.folio.linked.data.util.Constants.Cache.SETTINGS_ENTRIES; - -import org.folio.linked.data.e2e.base.IntegrationTest; -import org.folio.linked.data.service.tenant.TenantScopedExecutionService; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.CacheManager; - -@IntegrationTest -class ConfigurationClientCacheIT { - - @Autowired - private ConfigurationService configurationService; - @Autowired - private CacheManager cacheManager; - @Autowired - private TenantScopedExecutionService tenantScopedExecutionService; - - @BeforeEach - void setUp() { - clearCache(); - } - - @AfterEach - void tearDown() { - clearCache(); - } - - @Test - void shouldNotShareCacheAcrossTenants() { - // given - var tenant1 = TENANT_ID; - - // when - populate cache for tenant1 - tenantScopedExecutionService.execute(tenant1, () -> configurationService.getFolioHost()); - - // Verify tenant1 has cache entry - var cache = cacheManager.getCache(SETTINGS_ENTRIES); - assertThat(cache).isNotNull(); - var tenant1CacheKey = tenant1 + "_FOLIO_HOST"; - assertThat(cache.get(tenant1CacheKey)).isNotNull(); - - // when - check tenant2 cache before any call - var tenant2 = "another_tenant"; - var tenant2CacheKey = tenant2 + "_FOLIO_HOST"; - var tenant2CacheValue = cache.get(tenant2CacheKey); - - // then - tenant2 should not have any cached value yet - assertThat(tenant2CacheValue).isNull(); - - // when - make call for tenant2 - tenantScopedExecutionService.execute(tenant2, () -> configurationService.getFolioHost()); - - // then - now tenant2 should have its own cache entry - assertThat(cache.get(tenant2CacheKey)).isNotNull(); - - // Verify both tenants have separate cache entries - assertThat(cache.get(tenant1CacheKey)).isNotNull(); - assertThat(cache.get(tenant2CacheKey)).isNotNull(); - - // Verify they are different cache entries - assertThat(cache.get(tenant1CacheKey)).isNotEqualTo(cache.get(tenant2CacheKey)); - } - - - private void clearCache() { - var cache = cacheManager.getCache(SETTINGS_ENTRIES); - if (cache != null) { - cache.clear(); - } - } -} - diff --git a/src/test/java/org/folio/linked/data/integration/rest/configuration/ConfigurationServiceTest.java b/src/test/java/org/folio/linked/data/integration/rest/configuration/ConfigurationServiceTest.java deleted file mode 100644 index 451e9a207..000000000 --- a/src/test/java/org/folio/linked/data/integration/rest/configuration/ConfigurationServiceTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.folio.linked.data.integration.rest.configuration; - -import static java.util.Objects.nonNull; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.when; - -import java.util.List; -import org.folio.linked.data.domain.dto.Config; -import org.folio.linked.data.domain.dto.Configurations; -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 ConfigurationServiceTest { - - @InjectMocks - private ConfigurationServiceImpl configurationService; - @Mock - private ConfigurationClient configurationClient; - - @Test - void getFolioHost_returnsFolioHostFromConfigs() { - // given - var folioHost = "http://example.com"; - var configurations = getConfigurations("FOLIO_HOST", folioHost); - when(configurationClient.lookupConfig("USERSBL", "FOLIO_HOST")).thenReturn(configurations); - - // when - var result = configurationService.getFolioHost(); - - // then - assertThat(result).isEqualTo(folioHost); - } - - @Test - void getFolioHost_returnsDefaultFolioHostWhenConfigsAreEmpty() { - when(configurationClient.lookupConfig("USERSBL", "FOLIO_HOST")) - .thenReturn(getConfigurations(null, null)); - - var result = configurationService.getFolioHost(); - - assertThat(result).isEqualTo("http://localhost:8081"); - } - - private Configurations getConfigurations(String key, String value) { - var configurations = new Configurations(); - if (nonNull(key)) { - var config = new Config(); - config.setCode(key); - config.setValue(value); - configurations.setConfigs(List.of(config)); - } - configurations.setTotalRecords(1); - return configurations; - } -} diff --git a/src/test/java/org/folio/linked/data/integration/rest/settings/SettingsClientCacheIT.java b/src/test/java/org/folio/linked/data/integration/rest/settings/SettingsClientCacheIT.java index c073d3140..5426077d7 100644 --- a/src/test/java/org/folio/linked/data/integration/rest/settings/SettingsClientCacheIT.java +++ b/src/test/java/org/folio/linked/data/integration/rest/settings/SettingsClientCacheIT.java @@ -33,7 +33,7 @@ void tearDown() { } @Test - void shouldNotShareCacheAcrossTenants() { + void shouldNotShareEntriesCacheAcrossTenants() { // given var tenant1 = TENANT_ID; var query = "key==test.setting"; @@ -62,11 +62,43 @@ void shouldNotShareCacheAcrossTenants() { assertThat(cache.get(tenant2CacheKey)).isNotNull(); // Verify both tenants have separate cache entries with different keys + assertThat(cache.get(tenant1CacheKey).get()).isNotNull(); + assertThat(cache.get(tenant2CacheKey).get()).isNotNull(); + assertThat(cache.get(tenant1CacheKey).get()).isNotEqualTo(cache.get(tenant2CacheKey).get()); + } + + @Test + void shouldNotShareBaseUrlCacheAcrossTenants() { + // given + var tenant1 = TENANT_ID; + + // when - populate cache for tenant1 + tenantScopedExecutionService.execute(tenant1, () -> settingsClient.getBaseUrl()); + + // Verify tenant1 has cache entry + var cache = cacheManager.getCache(SETTINGS_ENTRIES); + assertThat(cache).isNotNull(); + var tenant1CacheKey = tenant1 + "_base-url"; assertThat(cache.get(tenant1CacheKey)).isNotNull(); + + // when - check tenant2 cache before any call + var tenant2 = "another_tenant"; + var tenant2CacheKey = tenant2 + "_base-url"; + var tenant2CacheValue = cache.get(tenant2CacheKey); + + // then - tenant2 should not have any cached value yet + assertThat(tenant2CacheValue).isNull(); + + // when - make call for tenant2 + tenantScopedExecutionService.execute(tenant2, () -> settingsClient.getBaseUrl()); + + // then - now tenant2 should have its own cache entry assertThat(cache.get(tenant2CacheKey)).isNotNull(); - // Verify that the cache keys are indeed different - assertThat(tenant1CacheKey).isNotEqualTo(tenant2CacheKey); + // Verify both tenants have separate cache entries + assertThat(cache.get(tenant1CacheKey).get()).isNotNull(); + assertThat(cache.get(tenant2CacheKey).get()).isNotNull(); + assertThat(cache.get(tenant1CacheKey).get()).isNotEqualTo(cache.get(tenant2CacheKey).get()); } private void clearCache() { @@ -76,4 +108,3 @@ private void clearCache() { } } } - diff --git a/src/test/java/org/folio/linked/data/integration/rest/specification/SpecClientCacheIT.java b/src/test/java/org/folio/linked/data/integration/rest/specification/SpecClientCacheIT.java index ff0b8517c..7244b5ec6 100644 --- a/src/test/java/org/folio/linked/data/integration/rest/specification/SpecClientCacheIT.java +++ b/src/test/java/org/folio/linked/data/integration/rest/specification/SpecClientCacheIT.java @@ -63,11 +63,10 @@ void shouldNotShareCacheAcrossTenants() { assertThat(cache.get(tenant2CacheKey)).isNotNull(); // Verify both tenants have separate cache entries with different keys - assertThat(cache.get(tenant1CacheKey)).isNotNull(); - assertThat(cache.get(tenant2CacheKey)).isNotNull(); - - // Verify that the cache keys are indeed different assertThat(tenant1CacheKey).isNotEqualTo(tenant2CacheKey); + assertThat(cache.get(tenant1CacheKey).get()).isNotNull(); + assertThat(cache.get(tenant2CacheKey).get()).isNotNull(); + assertThat(cache.get(tenant1CacheKey).get()).isNotEqualTo(cache.get(tenant2CacheKey).get()); } private void clearCaches() { @@ -81,4 +80,3 @@ private void clearCaches() { } } } - diff --git a/src/test/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapperTest.java b/src/test/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapperTest.java index 87423685c..aa2760220 100644 --- a/src/test/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapperTest.java +++ b/src/test/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapperTest.java @@ -5,7 +5,6 @@ import static org.folio.linked.data.test.TestUtil.randomLong; import static org.mockito.Mockito.doReturn; -import java.util.Map; import java.util.UUID; import org.folio.linked.data.domain.dto.InstanceIngressPayload; import org.folio.linked.data.mapper.ResourceModelMapper; @@ -59,9 +58,8 @@ void testMapping() { .hasFieldOrPropertyWithValue("sourceRecordIdentifier", srsId) .hasFieldOrPropertyWithValue("sourceType", InstanceIngressPayload.SourceTypeEnum.LINKED_DATA) .hasFieldOrPropertyWithValue("sourceRecordObject", marcString) - .hasFieldOrPropertyWithValue("additionalProperties", - Map.of("linkedDataId", instance.getId(), "instanceId", inventoryId) - ); + .hasFieldOrPropertyWithValue("linkedDataId", instance.getId()) + .hasFieldOrPropertyWithValue("instanceId", inventoryId); } } diff --git a/src/test/java/org/folio/linked/data/service/rdf/ResourceUrlProviderTest.java b/src/test/java/org/folio/linked/data/service/rdf/ResourceUrlProviderTest.java index 273e4578c..50731a2f7 100644 --- a/src/test/java/org/folio/linked/data/service/rdf/ResourceUrlProviderTest.java +++ b/src/test/java/org/folio/linked/data/service/rdf/ResourceUrlProviderTest.java @@ -3,7 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; -import org.folio.linked.data.integration.rest.configuration.ConfigurationService; +import org.folio.linked.data.domain.dto.BaseUrlDto; +import org.folio.linked.data.integration.rest.settings.SettingsClient; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,11 +20,11 @@ class ResourceUrlProviderTest { @InjectMocks private ResourceUrlProvider resourceUrlProvider; @Mock - private ConfigurationService configurationService; + private SettingsClient settingsClient; @BeforeEach void setUp() { - when(configurationService.getFolioHost()).thenReturn("http://localhost"); + when(settingsClient.getBaseUrl()).thenReturn(new BaseUrlDto("http://localhost")); } @Test @@ -39,4 +40,18 @@ void returnsCorrectUrlForValidId() { assertThat(result).isEqualTo(expectedUrl); } + @Test + void returnsCorrectUrlWhenBaseUrlHasTrailingSlash() { + // given + when(settingsClient.getBaseUrl()).thenReturn(new BaseUrlDto("http://localhost/")); + var id = 123L; + var expectedUrl = "http://localhost/linked-data-editor/resources/123"; + + // when + var result = resourceUrlProvider.apply(id); + + // then + assertThat(result).isEqualTo(expectedUrl); + } + } diff --git a/src/test/resources/mappings/base-url.json b/src/test/resources/mappings/base-url.json new file mode 100644 index 000000000..f966c2403 --- /dev/null +++ b/src/test/resources/mappings/base-url.json @@ -0,0 +1,40 @@ +{ + "mappings": [ + { + "request": { + "method": "GET", + "url": "/base-url", + "headers": { + "X-Okapi-Tenant": { + "equalTo": "test_tenant" + } + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": "{\n \"baseUrl\": \"https://folio-etesting-snapshot-diku.ci.folio.org\"\n}" + } + }, + { + "request": { + "method": "GET", + "url": "/base-url", + "headers": { + "X-Okapi-Tenant": { + "equalTo": "another_tenant" + } + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": "{\n \"baseUrl\": \"https://another-tenant-folio.example.com\"\n}" + } + } + ] +} diff --git a/src/test/resources/mappings/configurations.json b/src/test/resources/mappings/configurations.json deleted file mode 100644 index e29bd6daa..000000000 --- a/src/test/resources/mappings/configurations.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mappings": [ - { - "request": { - "method": "GET", - "url": "/configurations/entries?query=module%3D%3DUSERSBL%20and%20code%3D%3DFOLIO_HOST", - "headers": { - "X-Okapi-Tenant": { - "equalTo": "test_tenant" - } - } - }, - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": "{\n \"configs\": [\n {\n \"id\": \"bad0361b-7918-42fe-9bc0-e8901cf2a4f3\",\n \"module\": \"USERSBL\",\n \"configName\": \"FOLIO host\",\n \"code\": \"FOLIO_HOST\",\n \"description\": \"FOLIO host address for password reset\",\n \"default\": true,\n \"enabled\": true,\n \"value\": \"https://folio-etesting-snapshot-diku.ci.folio.org\",\n \"metadata\": {\n \"createdDate\": \"2025-08-25T03:22:38.750+00:00\",\n \"updatedDate\": \"2025-08-25T03:22:38.750+00:00\"\n }\n }\n ],\n \"totalRecords\": 1,\n \"resultInfo\": {\n \"totalRecords\": 1,\n \"facets\": [],\n \"diagnostics\": []\n }\n}" - } - }, - { - "request": { - "method": "GET", - "url": "/configurations/entries?query=module%3D%3DUSERSBL%20and%20code%3D%3DFOLIO_HOST", - "headers": { - "X-Okapi-Tenant": { - "equalTo": "another_tenant" - } - } - }, - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": "{\n \"configs\": [\n {\n \"id\": \"cfe1472c-8919-53af-0cd1-f9902dg3b5g4\",\n \"module\": \"USERSBL\",\n \"configName\": \"FOLIO host\",\n \"code\": \"FOLIO_HOST\",\n \"description\": \"FOLIO host address for password reset\",\n \"default\": true,\n \"enabled\": true,\n \"value\": \"https://another-tenant-folio.example.com\",\n \"metadata\": {\n \"createdDate\": \"2025-01-26T10:00:00.000+00:00\",\n \"updatedDate\": \"2025-01-26T10:00:00.000+00:00\"\n }\n }\n ],\n \"totalRecords\": 1,\n \"resultInfo\": {\n \"totalRecords\": 1,\n \"facets\": [],\n \"diagnostics\": []\n }\n}" - } - } - ] -}