diff --git a/src/main/java/org/folio/fqm/service/CrossTenantQueryService.java b/src/main/java/org/folio/fqm/service/CrossTenantQueryService.java index b575bbfa..c2e97abc 100644 --- a/src/main/java/org/folio/fqm/service/CrossTenantQueryService.java +++ b/src/main/java/org/folio/fqm/service/CrossTenantQueryService.java @@ -22,12 +22,15 @@ public class CrossTenantQueryService { private final SimpleHttpClient ecsClient; private final FolioExecutionContext executionContext; private final PermissionsService permissionsService; + private final UserTenantService userTenantService; private static final String COMPOSITE_INSTANCES_ID = "6b08439b-4f8e-4468-8046-ea620f5cfb74"; private static final String SIMPLE_INSTANCES_ID = "8fc4a9d2-7ccf-4233-afb8-796911839862"; public List getTenantsToQuery(EntityType entityType, boolean forceCrossTenantQuery) { - if (!forceCrossTenantQuery && !Boolean.TRUE.equals(entityType.getCrossTenantQueriesEnabled())) { + if (!forceCrossTenantQuery + && !Boolean.TRUE.equals(entityType.getCrossTenantQueriesEnabled()) + && !COMPOSITE_INSTANCES_ID.equals(entityType.getId())) { return List.of(executionContext.getTenantId()); } // Get the ECS tenant info first, since this comes from mod-users and should work in non-ECS environments @@ -82,6 +85,23 @@ private List> getUserTenants(String consortiumId, String use .parse(userTenantResponse) .read("$.userTenants", List.class); } + + public String getCentralTenantId() { + return getCentralTenantId(getEcsTenantInfo()); + } + + public boolean ecsEnabled() { + return ecsEnabled(getEcsTenantInfo()); + } + + public boolean isCentralTenant() { + return isCentralTenant(getEcsTenantInfo()); + } + + private boolean ecsEnabled(Map ecsTenantInfo) { + return !(ecsTenantInfo == null || ecsTenantInfo.isEmpty()); + } + /** * Retrieve the primary affiliation for a user. * This retrieves the primary affiliation for an arbitrary user in the tenant. @@ -90,7 +110,7 @@ private List> getUserTenants(String consortiumId, String use */ @SuppressWarnings("unchecked") // JsonPath.parse is returning a plain List without a type parameter, and the TypeRef (vs Class) parameter to JsonPath.read is not supported by the JSON parser private Map getEcsTenantInfo() { - String userTenantsResponse = ecsClient.get("user-tenants", Map.of("limit", "1")); + String userTenantsResponse = userTenantService.getUserTenantsResponse(executionContext.getTenantId()); List> userTenants = JsonPath .parse(userTenantsResponse) .read("$.userTenants", List.class); @@ -104,15 +124,7 @@ private String getCentralTenantId(Map ecsTenantInfo) { return ecsTenantInfo != null ? ecsTenantInfo.get("centralTenantId") : null; } - public String getCentralTenantId() { - return getCentralTenantId(getEcsTenantInfo()); - } - - private boolean ecsEnabled(Map ecsTenantInfo) { - return !(ecsTenantInfo == null || ecsTenantInfo.isEmpty()); - } - - public boolean ecsEnabled() { - return ecsEnabled(getEcsTenantInfo()); + private boolean isCentralTenant(Map ecsTenantInfo) { + return executionContext.getTenantId().equals(getCentralTenantId(ecsTenantInfo)); } } diff --git a/src/main/java/org/folio/fqm/service/EntityTypeFlatteningService.java b/src/main/java/org/folio/fqm/service/EntityTypeFlatteningService.java index 006f824e..99d4cfb2 100644 --- a/src/main/java/org/folio/fqm/service/EntityTypeFlatteningService.java +++ b/src/main/java/org/folio/fqm/service/EntityTypeFlatteningService.java @@ -5,7 +5,6 @@ import com.jayway.jsonpath.JsonPath; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.folio.fqm.client.SimpleHttpClient; import org.folio.fqm.exception.EntityTypeNotFoundException; import org.folio.fqm.exception.InvalidEntityTypeDefinitionException; import org.folio.fqm.repository.EntityTypeRepository; @@ -17,6 +16,7 @@ import org.folio.querytool.domain.dto.Field; import org.folio.querytool.domain.dto.NestedObjectProperty; import org.folio.querytool.domain.dto.ObjectType; +import org.folio.spring.FolioExecutionContext; import org.springframework.stereotype.Service; import java.util.*; @@ -31,7 +31,8 @@ public class EntityTypeFlatteningService { private final EntityTypeRepository entityTypeRepository; private final ObjectMapper objectMapper; private final LocalizationService localizationService; - private final SimpleHttpClient ecsClient; + private final FolioExecutionContext executionContext; + private final UserTenantService userTenantService; public EntityType getFlattenedEntityType(UUID entityTypeId, String tenantId) { return getFlattenedEntityType(entityTypeId, null, tenantId); @@ -317,8 +318,8 @@ private Stream getFilteredColumns(Stream unf } private boolean ecsEnabled() { - String rawJson = ecsClient.get("user-tenants", Map.of("limit", String.valueOf(1))); - DocumentContext parsedJson = JsonPath.parse(rawJson); + String userTenantsResponse = userTenantService.getUserTenantsResponse(executionContext.getTenantId()); + DocumentContext parsedJson = JsonPath.parse(userTenantsResponse); // The value isn't needed here, this just provides an easy way to tell if ECS is enabled int totalRecords = parsedJson.read("totalRecords", Integer.class); return totalRecords > 0; diff --git a/src/main/java/org/folio/fqm/service/EntityTypeService.java b/src/main/java/org/folio/fqm/service/EntityTypeService.java index 76e30675..34ec4f9e 100644 --- a/src/main/java/org/folio/fqm/service/EntityTypeService.java +++ b/src/main/java/org/folio/fqm/service/EntityTypeService.java @@ -60,7 +60,8 @@ public List getEntityTypeSummary(Set entityTypeIds, boo .map(entityType -> { EntityTypeSummary result = new EntityTypeSummary() .id(UUID.fromString(entityType.getId())) - .label(localizationService.getEntityTypeLabel(entityType.getName())); + .label(localizationService.getEntityTypeLabel(entityType.getName())) + .crossTenantQueriesEnabled(entityType.getCrossTenantQueriesEnabled()); if (includeInaccessible) { return result.missingPermissions( permissionsService.getRequiredPermissions(entityType) @@ -86,6 +87,8 @@ public List getEntityTypeSummary(Set entityTypeIds, boo */ public EntityType getEntityTypeDefinition(UUID entityTypeId, boolean includeHidden, boolean sortColumns) { EntityType entityType = entityTypeFlatteningService.getFlattenedEntityType(entityTypeId, null); + boolean crossTenantEnabled = Boolean.TRUE.equals(entityType.getCrossTenantQueriesEnabled()) + && crossTenantQueryService.isCentralTenant(); List columns = entityType .getColumns() .stream() @@ -96,7 +99,9 @@ public EntityType getEntityTypeDefinition(UUID entityTypeId, boolean includeHidd .sorted(nullsLast(comparing(Field::getLabelAlias, String.CASE_INSENSITIVE_ORDER))) .toList(); } - return entityType.columns(columns); + return entityType + .columns(columns) + .crossTenantQueriesEnabled(crossTenantEnabled); } /** diff --git a/src/main/java/org/folio/fqm/service/UserTenantService.java b/src/main/java/org/folio/fqm/service/UserTenantService.java new file mode 100644 index 00000000..45a47654 --- /dev/null +++ b/src/main/java/org/folio/fqm/service/UserTenantService.java @@ -0,0 +1,26 @@ +package org.folio.fqm.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.fqm.client.SimpleHttpClient; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * Service wrapper for caching responses from user-tenants API. + */ +@Service +@RequiredArgsConstructor +@Log4j2 +public class UserTenantService { + + private final SimpleHttpClient userTenantsClient; + + @Cacheable(value="userTenantCache", key="#tenantId") + public String getUserTenantsResponse(String tenantId) { + log.info("Retrieving user-tenants information for tenant {}", tenantId); + return userTenantsClient.get("user-tenants", Map.of("limit", String.valueOf(1))); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ec10bb95..2f77b6e0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -75,6 +75,7 @@ coffee-boots: cache: spec: queryCache: maximumSize=500,expireAfterWrite=1m + userTenantCache: maximumSize=100,expireAfterWrite=5h folio: is-eureka: false tenant: diff --git a/src/main/resources/swagger.api/schemas/EntityTypeSummary.json b/src/main/resources/swagger.api/schemas/EntityTypeSummary.json index 7511fb54..018f4c32 100644 --- a/src/main/resources/swagger.api/schemas/EntityTypeSummary.json +++ b/src/main/resources/swagger.api/schemas/EntityTypeSummary.json @@ -12,6 +12,11 @@ "description": "Entity type label", "type": "string" }, + "crossTenantQueriesEnabled": { + "description": "Indicates if this entity type supports cross-tenant queries", + "type": "boolean", + "default": false + }, "missingPermissions": { "description": "List of missing permissions", "type": "array", diff --git a/src/test/java/org/folio/fqm/service/CrossTenantQueryServiceTest.java b/src/test/java/org/folio/fqm/service/CrossTenantQueryServiceTest.java index acc989b9..ee7d7324 100644 --- a/src/test/java/org/folio/fqm/service/CrossTenantQueryServiceTest.java +++ b/src/test/java/org/folio/fqm/service/CrossTenantQueryServiceTest.java @@ -35,6 +35,9 @@ class CrossTenantQueryServiceTest { @Mock private PermissionsService permissionsService; + @Mock + private UserTenantService userTenantService; + @InjectMocks private CrossTenantQueryService crossTenantQueryService; @@ -59,17 +62,20 @@ class CrossTenantQueryServiceTest { { "id": "06192681-0df7-4f33-a38f-48e017648d69", "userId": "a5e7895f-503c-4335-8828-f507bc8d1c45", - "tenantId": "tenant_01" + "tenantId": "tenant_01", + "centralTenantId": "tenant_01" }, { "id": "3c1bfbe9-7d64-41fe-a358-cdaced6a631f", "userId": "a5e7895f-503c-4335-8828-f507bc8d1c45", - "tenantId": "tenant_02" + "tenantId": "tenant_02", + "centralTenantId": "tenant_01" }, { "id": "b167837a-ecdd-482b-b5d3-79a391a1dbf1", "userId": "a5e7895f-503c-4335-8828-f507bc8d1c45", "tenantId": "tenant_03", + "centralTenantId": "tenant_01" } ] } @@ -93,7 +99,7 @@ void shouldGetListOfTenantsToQuery() { List expectedTenants = List.of("tenant_01", "tenant_02", "tenant_03"); when(executionContext.getTenantId()).thenReturn(tenantId); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(ECS_TENANT_INFO); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(ECS_TENANT_INFO); when(ecsClient.get(eq("consortia/bdaa4720-5e11-4632-bc10-d4455cf252df/user-tenants"), anyMap())).thenReturn(USER_TENANT_JSON); List actualTenants = crossTenantQueryService.getTenantsToQuery(entityType, false); @@ -112,18 +118,20 @@ void shouldRunIntraTenantQueryForNonInstanceEntityTypes() { @Test void shouldRunIntraTenantQueryForNonCentralTenant() { - List expectedTenants = List.of("tenant_02"); - when(executionContext.getTenantId()).thenReturn("tenant_02"); // Central is tenant_01 - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(ECS_TENANT_INFO); + String tenantId = "tenant_02"; + List expectedTenants = List.of(tenantId); + when(executionContext.getTenantId()).thenReturn(tenantId); // Central is tenant_01 + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(ECS_TENANT_INFO); List actualTenants = crossTenantQueryService.getTenantsToQuery(entityType, false); assertEquals(expectedTenants, actualTenants); } @Test void shouldRunIntraTenantQueryIfExceptionIsThrown() { - List expectedTenants = List.of("tenant_01"); - when(executionContext.getTenantId()).thenReturn("tenant_01"); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(ECS_TENANT_INFO_FOR_NON_ECS_ENV); + String tenantId = "tenant_01"; + List expectedTenants = List.of(tenantId); + when(executionContext.getTenantId()).thenReturn(tenantId); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(ECS_TENANT_INFO_FOR_NON_ECS_ENV); List actualTenants = crossTenantQueryService.getTenantsToQuery(entityType, false); assertEquals(expectedTenants, actualTenants); } @@ -134,19 +142,32 @@ void shouldReturnTenantIdOnlyIfUserTenantsApiThrowsException() { List expectedTenants = List.of("tenant_01"); when(executionContext.getTenantId()).thenReturn(tenantId); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(ECS_TENANT_INFO_FOR_NON_ECS_ENV); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(ECS_TENANT_INFO_FOR_NON_ECS_ENV); List actualTenants = crossTenantQueryService.getTenantsToQuery(entityType, false); assertEquals(expectedTenants, actualTenants); } + @Test + void shouldAttemptCrossTenantQueryIfForceParamIsTrue() { + String tenantId = "tenant_01"; + List expectedTenants = List.of("tenant_01"); + + when(executionContext.getTenantId()).thenReturn(tenantId); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(ECS_TENANT_INFO_FOR_NON_ECS_ENV); + + List actualTenants = crossTenantQueryService.getTenantsToQuery(entityType, true); + verify(userTenantService, times(1)).getUserTenantsResponse(tenantId); + assertEquals(expectedTenants, actualTenants); + } + @Test void shouldNotQueryTenantIfUserLacksTenantPermissions() { String tenantId = "tenant_01"; List expectedTenants = List.of("tenant_01", "tenant_02"); when(executionContext.getTenantId()).thenReturn(tenantId); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(ECS_TENANT_INFO); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(ECS_TENANT_INFO); when(ecsClient.get(eq("consortia/bdaa4720-5e11-4632-bc10-d4455cf252df/user-tenants"), anyMap())).thenReturn(USER_TENANT_JSON); doNothing().when(permissionsService).verifyUserHasNecessaryPermissions("tenant_02", entityType, true); doThrow(MissingPermissionsException.class).when(permissionsService).verifyUserHasNecessaryPermissions("tenant_03", entityType, true); @@ -163,7 +184,7 @@ void shouldQueryCentralTenantForSharedCompositeInstances() { .crossTenantQueriesEnabled(true); when(executionContext.getTenantId()).thenReturn(tenantId); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(ECS_TENANT_INFO); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(ECS_TENANT_INFO); List actualTenants = crossTenantQueryService.getTenantsToQuery(instanceEntityType, false); assertEquals(expectedTenants, actualTenants); @@ -171,15 +192,26 @@ void shouldQueryCentralTenantForSharedCompositeInstances() { @Test void shouldGetCentralTenantId() { - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(ECS_TENANT_INFO); String expectedId = "tenant_01"; + when(executionContext.getTenantId()).thenReturn(expectedId); + when(userTenantService.getUserTenantsResponse(expectedId)).thenReturn(ECS_TENANT_INFO); String actualId = crossTenantQueryService.getCentralTenantId(); assertEquals(expectedId, actualId); } @Test void shouldHandleErrorWhenGettingCentralTenantId() { - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(ECS_TENANT_INFO_FOR_NON_ECS_ENV); + String tenantId = "tenant_01"; + when(executionContext.getTenantId()).thenReturn(tenantId); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(ECS_TENANT_INFO_FOR_NON_ECS_ENV); assertNull(crossTenantQueryService.getCentralTenantId()); } + + @Test + void testIsCentralTenant() { + String tenantId = "tenant_01"; + when(executionContext.getTenantId()).thenReturn(tenantId); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(USER_TENANT_JSON); + assertTrue(crossTenantQueryService.isCentralTenant()); + } } diff --git a/src/test/java/org/folio/fqm/service/EntityTypeFlatteningServiceTest.java b/src/test/java/org/folio/fqm/service/EntityTypeFlatteningServiceTest.java index ec2c8960..5deee40e 100644 --- a/src/test/java/org/folio/fqm/service/EntityTypeFlatteningServiceTest.java +++ b/src/test/java/org/folio/fqm/service/EntityTypeFlatteningServiceTest.java @@ -1,7 +1,6 @@ package org.folio.fqm.service; import com.fasterxml.jackson.databind.ObjectMapper; -import org.folio.fqm.client.SimpleHttpClient; import org.folio.fqm.repository.EntityTypeRepository; import org.folio.querytool.domain.dto.ArrayType; import org.folio.querytool.domain.dto.EntityType; @@ -20,7 +19,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -36,7 +34,7 @@ class EntityTypeFlatteningServiceTest { @Mock private LocalizationService localizationService; @Mock - private SimpleHttpClient ecsClient; + private UserTenantService userTenantService; @Mock private FolioExecutionContext executionContext; @@ -45,7 +43,7 @@ class EntityTypeFlatteningServiceTest { @BeforeEach void setup() { ObjectMapper objectMapper = new ObjectMapper(); - entityTypeFlatteningService = new EntityTypeFlatteningService(entityTypeRepository, objectMapper, localizationService, ecsClient); + entityTypeFlatteningService = new EntityTypeFlatteningService(entityTypeRepository, objectMapper, localizationService, executionContext, userTenantService); } private static final UUID SIMPLE_ENTITY_TYPE_ID = UUID.fromString("0686b9e4-accd-46f8-9e35-792c735733bb"); @@ -443,7 +441,8 @@ void shouldFlattenSimpleEntityType() { when(entityTypeRepository.getEntityTypeDefinition(SIMPLE_ENTITY_TYPE_ID, null)).thenReturn(Optional.of(copyEntityType(SIMPLE_ENTITY_TYPE))); when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn("{'totalRecords': 0}"); + when(executionContext.getTenantId()).thenReturn("tenant_01"); + when(userTenantService.getUserTenantsResponse("tenant_01")).thenReturn("{'totalRecords': 0}"); EntityType actualEntityType = entityTypeFlatteningService.getFlattenedEntityType(SIMPLE_ENTITY_TYPE_ID, null); assertEquals(expectedEntityType, actualEntityType); } @@ -637,7 +636,8 @@ void shouldFlattenComplexEntityType() { } return entityType; }); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn("{'totalRecords': 0}"); + when(executionContext.getTenantId()).thenReturn("tenant_01"); + when(userTenantService.getUserTenantsResponse("tenant_01")).thenReturn("{'totalRecords': 0}"); EntityType actualEntityType = entityTypeFlatteningService.getFlattenedEntityType(COMPLEX_ENTITY_TYPE_ID, null); assertEquals(expectedEntityType, actualEntityType); @@ -846,7 +846,8 @@ void shouldFlattenTripleNestedEntityType() { when(entityTypeRepository.getEntityTypeDefinition(COMPLEX_ENTITY_TYPE_ID, null)).thenReturn(Optional.of(copyEntityType(COMPLEX_ENTITY_TYPE))); when(entityTypeRepository.getEntityTypeDefinition(TRIPLE_NESTED_ENTITY_TYPE_ID, null)).thenReturn(Optional.of(copyEntityType(TRIPLE_NESTED_ENTITY_TYPE))); when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn("{'totalRecords': 0}"); + when(executionContext.getTenantId()).thenReturn("tenant_01"); + when(userTenantService.getUserTenantsResponse("tenant_01")).thenReturn("{'totalRecords': 0}"); EntityType actualEntityType = entityTypeFlatteningService.getFlattenedEntityType(TRIPLE_NESTED_ENTITY_TYPE_ID, null); assertEquals(expectedEntityType, actualEntityType); @@ -859,7 +860,8 @@ void shouldGetJoinClause() { when(entityTypeRepository.getEntityTypeDefinition(SIMPLE_ENTITY_TYPE_ID, null)).thenReturn(Optional.of(copyEntityType(SIMPLE_ENTITY_TYPE))); when(entityTypeRepository.getEntityTypeDefinition(COMPLEX_ENTITY_TYPE_ID, null)).thenReturn(Optional.of(copyEntityType(COMPLEX_ENTITY_TYPE))); when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn("{'totalRecords': 0}"); + when(executionContext.getTenantId()).thenReturn("tenant_01"); + when(userTenantService.getUserTenantsResponse("tenant_01")).thenReturn("{'totalRecords': 0}"); EntityType entityType = entityTypeFlatteningService.getFlattenedEntityType(COMPLEX_ENTITY_TYPE_ID, null); String actualJoinClause = entityTypeFlatteningService.getJoinClause(entityType, null); @@ -873,7 +875,8 @@ void shouldReorderSourcesToMakeValidJoinClause() { when(entityTypeRepository.getEntityTypeDefinition(UNORDERED_ENTITY_TYPE_ID, null)).thenReturn(Optional.of(copyEntityType(UNORDERED_ENTITY_TYPE))); when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn("{'totalRecords': 0}"); + when(executionContext.getTenantId()).thenReturn("tenant_01"); + when(userTenantService.getUserTenantsResponse("tenant_01")).thenReturn("{'totalRecords': 0}"); EntityType entityType = entityTypeFlatteningService.getFlattenedEntityType(UNORDERED_ENTITY_TYPE_ID, null); String actualJoinClause = entityTypeFlatteningService.getJoinClause(entityType, null); @@ -962,7 +965,8 @@ void shouldIncludeEcsColumnsWhenEcsIsEnabled() { when(entityTypeRepository.getEntityTypeDefinition(SIMPLE_ENTITY_TYPE_ID, null)).thenReturn(Optional.of(copyEntityType(SIMPLE_ENTITY_TYPE))); when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn("{'totalRecords': 1}"); + when(executionContext.getTenantId()).thenReturn("tenant_01"); + when(userTenantService.getUserTenantsResponse("tenant_01")).thenReturn("{'totalRecords': 1}"); EntityType actualEntityType = entityTypeFlatteningService.getFlattenedEntityType(SIMPLE_ENTITY_TYPE_ID, null); assertEquals(expectedEntityType, actualEntityType); @@ -1040,7 +1044,8 @@ void shouldFlattenSimpleEntityTypeWithSourceViewExtractor() { .requiredPermissions(List.of("simple_permission1", "simple_permission2")) .sourceViewExtractor("some_view_extractor"); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn("{'totalRecords': 0}"); + when(executionContext.getTenantId()).thenReturn("tenant_01"); + when(userTenantService.getUserTenantsResponse("tenant_01")).thenReturn("{'totalRecords': 0}"); when(entityTypeRepository.getEntityTypeDefinition(SIMPLE_ENTITY_TYPE_ID, null)) .thenReturn(Optional.of(copyEntityType(SIMPLE_ENTITY_TYPE_WITH_SOURCE_VIEW_EXTRACTOR))); diff --git a/src/test/java/org/folio/fqm/service/EntityTypeServiceTest.java b/src/test/java/org/folio/fqm/service/EntityTypeServiceTest.java index fda10fda..d903777b 100644 --- a/src/test/java/org/folio/fqm/service/EntityTypeServiceTest.java +++ b/src/test/java/org/folio/fqm/service/EntityTypeServiceTest.java @@ -368,6 +368,46 @@ void shouldReturnEntityTypeDefinition() { assertEquals(expectedEntityType, actualDefinition); } + @Test + void shouldReturnCrossTenantDefinitionWhenEcsEnabled() { + UUID entityTypeId = UUID.randomUUID(); + EntityType entityType = new EntityType() + .id(entityTypeId.toString()) + .crossTenantQueriesEnabled(true); + EntityType expectedEntityType = new EntityType() + .id(entityTypeId.toString()) + .crossTenantQueriesEnabled(true); + + when(entityTypeFlatteningService.getFlattenedEntityType(entityTypeId, null)) + .thenReturn(entityType); + when(crossTenantQueryService.isCentralTenant()).thenReturn(true); + + EntityType actualEntityType = entityTypeService + .getEntityTypeDefinition(entityTypeId, false, false); + + assertEquals(expectedEntityType, actualEntityType); + } + + @Test + void shouldReturnNonCrossTenantDefinitionWhenEcsNotEnabled() { + UUID entityTypeId = UUID.randomUUID(); + EntityType entityType = new EntityType() + .id(entityTypeId.toString()) + .crossTenantQueriesEnabled(true); + EntityType expectedEntityType = new EntityType() + .id(entityTypeId.toString()) + .crossTenantQueriesEnabled(false); + + when(entityTypeFlatteningService.getFlattenedEntityType(entityTypeId, null)) + .thenReturn(entityType); + when(crossTenantQueryService.isCentralTenant()).thenReturn(false); + + EntityType actualEntityType = entityTypeService + .getEntityTypeDefinition(entityTypeId, false, false); + + assertEquals(expectedEntityType, actualEntityType); + } + @Test void shouldReturnCurrencies() { UUID entityTypeId = UUID.randomUUID(); diff --git a/src/test/java/org/folio/fqm/service/UserTenantServiceTest.java b/src/test/java/org/folio/fqm/service/UserTenantServiceTest.java new file mode 100644 index 00000000..e36cf152 --- /dev/null +++ b/src/test/java/org/folio/fqm/service/UserTenantServiceTest.java @@ -0,0 +1,29 @@ +package org.folio.fqm.service; + +import org.folio.fqm.client.SimpleHttpClient; +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; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserTenantServiceTest { + + @Mock + private SimpleHttpClient userTenantsClient; + @InjectMocks + private UserTenantService userTenantService; + @Test + void shouldGetUserTenantsResponse() { + String expectedResponse = "{\"totalRecords\": 1}"; + when(userTenantsClient.get(eq("user-tenants"), anyMap())).thenReturn(expectedResponse); + String actualResponse = userTenantService.getUserTenantsResponse("tenant_01"); + assertEquals(expectedResponse, actualResponse); + } +} diff --git a/src/test/java/org/folio/fqm/utils/IdStreamerTest.java b/src/test/java/org/folio/fqm/utils/IdStreamerTest.java index efac52ea..71b88feb 100644 --- a/src/test/java/org/folio/fqm/utils/IdStreamerTest.java +++ b/src/test/java/org/folio/fqm/utils/IdStreamerTest.java @@ -25,6 +25,8 @@ import org.folio.fqm.service.CrossTenantQueryService; import org.folio.fqm.service.EntityTypeFlatteningService; import org.folio.fqm.service.LocalizationService; +import org.folio.fqm.service.PermissionsService; +import org.folio.fqm.service.UserTenantService; import org.folio.querytool.domain.dto.EntityType; import org.folio.spring.FolioExecutionContext; import org.jooq.DSLContext; @@ -46,6 +48,7 @@ class IdStreamerTest { private LocalizationService localizationService; private FolioExecutionContext executionContext; private SimpleHttpClient ecsClient; + private UserTenantService userTenantService; private static final String USER_TENANT_JSON = """ { @@ -54,19 +57,22 @@ class IdStreamerTest { "id": "06192681-0df7-4f33-a38f-48e017648d69", "userId": "a5e7895f-503c-4335-8828-f507bc8d1c45", "tenantId": "tenant_01", - "centralTenantId": "tenant_01" + "centralTenantId": "tenant_01", + "consortiumId": "0e88ed41-eadb-44c3-a7a7-f6572bbe06fc" }, { "id": "3c1bfbe9-7d64-41fe-a358-cdaced6a631f", "userId": "a5e7895f-503c-4335-8828-f507bc8d1c45", "tenantId": "tenant_02", - "centralTenantId": "tenant_01" + "centralTenantId": "tenant_01", + "consortiumId": "0e88ed41-eadb-44c3-a7a7-f6572bbe06fc" }, { "id": "b167837a-ecdd-482b-b5d3-79a391a1dbf1", "userId": "a5e7895f-503c-4335-8828-f507bc8d1c45", "tenantId": "tenant_03", - "centralTenantId": "tenant_01" + "centralTenantId": "tenant_01", + "consortiumId": "0e88ed41-eadb-44c3-a7a7-f6572bbe06fc" } ], "totalRecords": 3 @@ -100,9 +106,12 @@ void setup() { 0); localizationService = mock(LocalizationService.class); ecsClient = mock(SimpleHttpClient.class); + userTenantService = mock(UserTenantService.class); + PermissionsService permissionsService = mock(PermissionsService.class); - EntityTypeFlatteningService entityTypeFlatteningService = new EntityTypeFlatteningService(entityTypeRepository, new ObjectMapper(), localizationService, ecsClient); - CrossTenantQueryService crossTenantQueryService = new CrossTenantQueryService(ecsClient, executionContext, null); + + EntityTypeFlatteningService entityTypeFlatteningService = new EntityTypeFlatteningService(entityTypeRepository, new ObjectMapper(), localizationService, executionContext, userTenantService); + CrossTenantQueryService crossTenantQueryService = new CrossTenantQueryService(ecsClient, executionContext, permissionsService, userTenantService); this.idStreamer = new IdStreamer( context, @@ -114,6 +123,7 @@ void setup() { @Test void shouldFetchIdStreamForFql() { + String tenantId = "tenant_01"; Fql fql = new Fql("", new EqualsCondition(new FqlField("field1"), "value1")); List> expectedIds = new ArrayList<>(); TEST_CONTENT_IDS.forEach(contentId -> expectedIds.add(List.of(contentId.toString()))); @@ -123,8 +133,8 @@ void shouldFetchIdStreamForFql() { ids.forEach(idSet -> actualIds.add(Arrays.asList(idSet))); }; when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); - when(executionContext.getTenantId()).thenReturn("tenant_01"); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(NON_ECS_USER_TENANT_JSON); + when(executionContext.getTenantId()).thenReturn(tenantId); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(NON_ECS_USER_TENANT_JSON); int idsCount = idStreamer.streamIdsInBatch( IdStreamerTestDataProvider.TEST_ENTITY_TYPE_DEFINITION, true, @@ -138,6 +148,7 @@ void shouldFetchIdStreamForFql() { @Test void shouldUseAdditionalEcsConditionsInEcsEnvironment() { + String tenantId = "tenant_01"; Fql fql = new Fql("", new EqualsCondition(new FqlField("field1"), "value1")); List> expectedIds = List.of( List.of("ecsValue") @@ -148,8 +159,10 @@ void shouldUseAdditionalEcsConditionsInEcsEnvironment() { ids.forEach(idSet -> actualIds.add(Arrays.asList(idSet))); }; when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(USER_TENANT_JSON); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(USER_TENANT_JSON); + when(ecsClient.get(eq("consortia/0e88ed41-eadb-44c3-a7a7-f6572bbe06fc/user-tenants"), anyMap())).thenReturn(USER_TENANT_JSON); when(executionContext.getTenantId()).thenReturn("tenant_01"); + idStreamer.streamIdsInBatch( new EntityType().additionalEcsConditions(List.of("condition 1")).id("6b08439b-4f8e-4468-8046-ea620f5cfb74"), true, @@ -162,6 +175,7 @@ void shouldUseAdditionalEcsConditionsInEcsEnvironment() { @Test void shouldUseUnionAllForCrossTenantQuery() { + String tenantId = "tenant_01"; when(executionContext.getTenantId()).thenReturn("tenant_01"); Fql fql = new Fql("", new EqualsCondition(new FqlField("field1"), "value1")); List> expectedIds = new ArrayList<>(); @@ -173,7 +187,8 @@ void shouldUseUnionAllForCrossTenantQuery() { }; when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); when(executionContext.getTenantId()).thenReturn("tenant_01"); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(USER_TENANT_JSON); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(USER_TENANT_JSON); + when(ecsClient.get(eq("consortia/0e88ed41-eadb-44c3-a7a7-f6572bbe06fc/user-tenants"), anyMap())).thenReturn(USER_TENANT_JSON); int idsCount = idStreamer.streamIdsInBatch( IdStreamerTestDataProvider.TEST_ENTITY_TYPE_DEFINITION, true, @@ -187,6 +202,7 @@ void shouldUseUnionAllForCrossTenantQuery() { @Test void shouldHandleGroupByFields() { + String tenantId = "tenant_01"; Fql fql = new Fql("", new EqualsCondition(new FqlField("field1"), "value1")); List> expectedIds = new ArrayList<>(); TEST_CONTENT_IDS.forEach(contentId -> expectedIds.add(List.of(contentId.toString()))); @@ -197,7 +213,7 @@ void shouldHandleGroupByFields() { }; when(localizationService.localizeEntityType(any(EntityType.class), anyBoolean())).thenReturn(TEST_GROUP_BY_ENTITY_TYPE_DEFINITION); when(executionContext.getTenantId()).thenReturn("tenant_01"); - when(ecsClient.get(eq("user-tenants"), anyMap())).thenReturn(NON_ECS_USER_TENANT_JSON); + when(userTenantService.getUserTenantsResponse(tenantId)).thenReturn(NON_ECS_USER_TENANT_JSON); int idsCount = idStreamer.streamIdsInBatch( IdStreamerTestDataProvider.TEST_GROUP_BY_ENTITY_TYPE_DEFINITION, true, diff --git a/src/test/java/org/folio/fqm/utils/IdStreamerTestDataProvider.java b/src/test/java/org/folio/fqm/utils/IdStreamerTestDataProvider.java index 6a63f099..9d87dc4e 100644 --- a/src/test/java/org/folio/fqm/utils/IdStreamerTestDataProvider.java +++ b/src/test/java/org/folio/fqm/utils/IdStreamerTestDataProvider.java @@ -71,7 +71,8 @@ public class IdStreamerTestDataProvider implements MockDataProvider { private static final String GET_SORTED_IDS_QUERY_REGEX = "SELECT RESULT_ID FROM .* WHERE .* ORDER BY RESULT_ID .*"; private static final String GET_ENTITY_TYPE_ID_FROM_QUERY_ID_REGEX = "SELECT ENTITY_TYPE_ID FROM QUERY_DETAILS WHERE QUERY_ID = .*"; private static final String GET_IDS_REGEX = ".*QUERY_RESULTS.*"; - private static final String ADDITIONAL_ECS_REGEX = "SELECT CAST.* AS VARCHAR.* WHERE .* AND .*CONDITION 1.*"; + private static final String ADDITIONAL_ECS_REGEX = ".*SELECT CAST.* AS VARCHAR.* WHERE .* AND .*CONDITION 1.*"; + Pattern GET_IDS_PATTERN = Pattern.compile(GET_IDS_QUERY_REGEX, Pattern.DOTALL); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();