From 77d207775a0b35cfa450f1bb815ad4447a7cf43c Mon Sep 17 00:00:00 2001 From: Viachaslau Khandramai Date: Tue, 10 Feb 2026 13:20:15 +0300 Subject: [PATCH 1/2] MODBULKOPS-627 - ECS | Upload of Inventory records by UUIDs fails in Central, Member tenants --- .../folio/bulkops/util/FqmContentFetcher.java | 12 +- .../util/FqmContentFetcherEcsTest.java | 128 ++++++++++++++++++ .../bulkops/util/FqmContentFetcherTest.java | 2 + 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java diff --git a/src/main/java/org/folio/bulkops/util/FqmContentFetcher.java b/src/main/java/org/folio/bulkops/util/FqmContentFetcher.java index c1fe5ff0e..bb282059f 100644 --- a/src/main/java/org/folio/bulkops/util/FqmContentFetcher.java +++ b/src/main/java/org/folio/bulkops/util/FqmContentFetcher.java @@ -84,6 +84,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -159,7 +160,7 @@ public InputStream contents( List entityJsonKeys = getEntityJsonKeys(entityType); - boolean isCentralTenant = consortiaService.isTenantCentral(folioExecutionContext.getTenantId()); + var tenantId = folioExecutionContext.getTenantId(); List> chunks = IntStream.range(0, uuids.size()) @@ -170,6 +171,13 @@ public InputStream contents( ExecutorService pool = Executors.newFixedThreadPool(maxParallelChunks); CompletionService>> completion = new ExecutorCompletionService<>(pool); + String centralTenantId = consortiaService.getCentralTenantId(tenantId); + boolean isTenantInConsortia = StringUtils.isNotEmpty(centralTenantId); + boolean isCentralTenant = tenantId.equals(centralTenantId); + + Function> idMapper = + isTenantInConsortia ? id -> List.of(id, tenantId) : List::of; + for (List chunk : chunks) { completion.submit( () -> { @@ -177,7 +185,7 @@ public InputStream contents( new ContentsRequest() .entityTypeId(entityTypeId) .fields(entityJsonKeys) - .ids(chunk.stream().map(UUID::toString).map(List::of).toList()); + .ids(chunk.stream().map(UUID::toString).map(idMapper).toList()); return queryClient.getContents(req); }); } diff --git a/src/test/java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java b/src/test/java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java new file mode 100644 index 000000000..b0e575ddf --- /dev/null +++ b/src/test/java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java @@ -0,0 +1,128 @@ +package org.folio.bulkops.util; + +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import org.folio.bulkops.BaseTest; +import org.folio.bulkops.client.QueryClient; +import org.folio.bulkops.domain.dto.EntityType; +import org.folio.bulkops.service.ConsortiaService; +import org.folio.bulkops.service.EntityTypeService; +import org.folio.querytool.domain.dto.ContentsRequest; +import org.folio.spring.FolioExecutionContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +@SpringBootTest +@ContextConfiguration(initializers = BaseTest.Initializer.class) +public class FqmContentFetcherEcsTest { + + @MockitoBean private ConsortiaService consortiaService; + @MockitoBean private EntityTypeService entityTypeService; + @MockitoBean private FolioExecutionContext folioExecutionContext; + @MockitoBean private QueryClient queryClient; + + @Value("${application.fqm-fetcher.max_chunk_size}") + private int chunkSize; + + @Autowired private FqmContentFetcher fqmContentFetcher; + + @Test + void testCentralTenantBehavior() { + String tenantId = "central"; + String centralTenantId = "central"; + mockCommon(tenantId, centralTenantId); + + UUID uuid = UUID.randomUUID(); + + try (var is = + fqmContentFetcher.contents( + List.of(uuid), EntityType.INSTANCE, List.of(), UUID.randomUUID())) { + ArgumentCaptor captor = ArgumentCaptor.forClass(ContentsRequest.class); + + await() + .atMost(2, TimeUnit.SECONDS) + .untilAsserted(() -> verify(queryClient, atLeastOnce()).getContents(captor.capture())); + + ContentsRequest req = captor.getValue(); + List> ids = req.getIds(); + Assertions.assertEquals(1, ids.size()); + Assertions.assertEquals(List.of(uuid.toString(), tenantId), ids.getFirst()); + } catch (IOException e) { + Assertions.fail("Fail reading content"); + } + } + + @Test + void testConsortiumMemberTenantBehavior() { + String tenantId = "member"; + String centralTenantId = "central"; + mockCommon(tenantId, centralTenantId); + + UUID uuid = UUID.randomUUID(); + + try (var is = + fqmContentFetcher.contents( + List.of(uuid), EntityType.INSTANCE, List.of(), UUID.randomUUID())) { + ArgumentCaptor captor = ArgumentCaptor.forClass(ContentsRequest.class); + + await() + .atMost(2, TimeUnit.SECONDS) + .untilAsserted(() -> verify(queryClient, atLeastOnce()).getContents(captor.capture())); + + ContentsRequest req = captor.getValue(); + List> ids = req.getIds(); + Assertions.assertEquals(1, ids.size()); + Assertions.assertEquals(List.of(uuid.toString(), tenantId), ids.getFirst()); + } catch (IOException e) { + Assertions.fail("Fail reading content"); + } + } + + @Test + void testNonConsortiaTenantBehavior() { + String tenantId = "regular"; + String centralTenantId = ""; + mockCommon(tenantId, centralTenantId); + + UUID uuid = UUID.randomUUID(); + + try (var is = + fqmContentFetcher.contents( + List.of(uuid), EntityType.INSTANCE, List.of(), UUID.randomUUID())) { + ArgumentCaptor captor = ArgumentCaptor.forClass(ContentsRequest.class); + await() + .atMost(2, TimeUnit.SECONDS) + .untilAsserted(() -> verify(queryClient, atLeastOnce()).getContents(captor.capture())); + + ContentsRequest req = captor.getValue(); + List> ids = req.getIds(); + Assertions.assertEquals(1, ids.size()); + Assertions.assertEquals(List.of(uuid.toString()), ids.getFirst()); + } catch (IOException e) { + Assertions.fail("Fail reading content"); + } + } + + private void mockCommon(String tenantId, String centralTenantId) { + when(folioExecutionContext.getTenantId()).thenReturn(tenantId); + when(consortiaService.getCentralTenantId(tenantId)).thenReturn(centralTenantId); + when(entityTypeService.getFqmEntityTypeIdByBulkOpsEntityType(any())) + .thenReturn(UUID.randomUUID()); + when(queryClient.getContents(any())).thenReturn(List.of(Map.of())); + } +} diff --git a/src/test/java/org/folio/bulkops/util/FqmContentFetcherTest.java b/src/test/java/org/folio/bulkops/util/FqmContentFetcherTest.java index 4a9d4f012..94067576b 100644 --- a/src/test/java/org/folio/bulkops/util/FqmContentFetcherTest.java +++ b/src/test/java/org/folio/bulkops/util/FqmContentFetcherTest.java @@ -519,6 +519,7 @@ void fetchShouldReturnAnErrorForNonSharedInstancesInEcs(EntityType entityType) t when(folioExecutionContext.getTenantId()).thenReturn("tenant"); when(consortiaService.isTenantCentral(anyString())).thenReturn(true); + when(consortiaService.getCentralTenantId(anyString())).thenReturn("tenant"); when(entityTypeService.getFqmEntityTypeIdByBulkOpsEntityType(entityType)) .thenReturn(randomUUID()); @@ -555,6 +556,7 @@ void fetchShouldReturnAnErrorForShadowUsersInEcs() throws Exception { when(folioExecutionContext.getTenantId()).thenReturn("tenant"); when(consortiaService.isTenantCentral(anyString())).thenReturn(true); + when(consortiaService.getCentralTenantId(anyString())).thenReturn("tenant"); when(entityTypeService.getFqmEntityTypeIdByBulkOpsEntityType(EntityType.USER)) .thenReturn(randomUUID()); From 059ae24ebee5d9f2e9bbbe898c619a27350a2b3b Mon Sep 17 00:00:00 2001 From: Viachaslau Khandramai Date: Tue, 10 Feb 2026 13:35:36 +0300 Subject: [PATCH 2/2] MODBULKOPS-627 - ECS | Upload of Inventory records by UUIDs fails in Central, Member tenants --- .../java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java b/src/test/java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java index b0e575ddf..7f93d32da 100644 --- a/src/test/java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java +++ b/src/test/java/org/folio/bulkops/util/FqmContentFetcherEcsTest.java @@ -29,7 +29,7 @@ @SpringBootTest @ContextConfiguration(initializers = BaseTest.Initializer.class) -public class FqmContentFetcherEcsTest { +class FqmContentFetcherEcsTest { @MockitoBean private ConsortiaService consortiaService; @MockitoBean private EntityTypeService entityTypeService;