From 296cf902b946ce359c40dadcdce86d7085efb35e Mon Sep 17 00:00:00 2001 From: airajena Date: Sun, 15 Feb 2026 10:19:46 +0530 Subject: [PATCH] FINERACT-1878: Separate profile image paths for staff and clients --- .../ImageWritePlatformServiceImpl.java | 18 ++- .../ImageWritePlatformServiceImplTest.java | 115 ++++++++++++++++++ 2 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImplTest.java diff --git a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImpl.java b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImpl.java index 1538788be..6d25a80bd 100644 --- a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImpl.java +++ b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImpl.java @@ -65,6 +65,7 @@ public class ImageWritePlatformServiceImpl implements ImageWritePlatformService // TODO: keeping the path segment always "clients" not consistent how this works with documents request.setEntityType(DEFAULT_ENTITY_TYPE); } + final var imageEntityType = normalizeImageEntityType(request.getEntityType()); if (StringUtils.isEmpty(request.getFileName())) { // NOTE: defacto limiting the uploads to JPEG files, same behavior as before @@ -83,12 +84,11 @@ public class ImageWritePlatformServiceImpl implements ImageWritePlatformService } // TODO: make "prefix" configurable? - var path = getPath(STORE_PREFIX, request.getEntityType(), request.getEntityId(), request.getFileName(), - storeService.getDelimiter()); + var path = getPath(STORE_PREFIX, imageEntityType, request.getEntityId(), request.getFileName(), storeService.getDelimiter()); final var imagePath = storeService.upload(path, request.getStream(), request.getType()); - final var result = imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(request.getEntityType())).findFirst() + final var result = imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(imageEntityType)).findFirst() .flatMap(imageIdAdapter -> imageIdAdapter.get(request.getEntityId())) .flatMap(imageIdResult -> imageRepository.findById(imageIdResult.getId())).map(image -> { // delete old image @@ -99,7 +99,7 @@ public class ImageWritePlatformServiceImpl implements ImageWritePlatformService .map(image -> image.setLocation(imagePath).setStorageType(storeService.getType().getValue())).map(imageRepository::save) .map(image -> ImageCreateResponse.builder().resourceId(image.getId()).build()); - imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(request.getEntityType())).findFirst() + imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(imageEntityType)).findFirst() .ifPresent(imageIdAdapter -> result.ifPresent( imageCreateResponse -> imageIdAdapter.set(request.getEntityId(), imageCreateResponse.getResourceId()))); @@ -135,6 +135,16 @@ public class ImageWritePlatformServiceImpl implements ImageWritePlatformService }); } + private String normalizeImageEntityType(final String entityType) { + if ("staff".equalsIgnoreCase(entityType)) { + return "staff"; + } + if ("clients".equalsIgnoreCase(entityType)) { + return DEFAULT_ENTITY_TYPE; + } + return entityType; + } + private String getPath(final String prefix, final String entityType, final Long entityId, final String fileName, String delimiter) { requireNonNull(prefix); requireNonNull(entityType); diff --git a/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImplTest.java b/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImplTest.java new file mode 100644 index 000000000..a3c71c2c8 --- /dev/null +++ b/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImplTest.java @@ -0,0 +1,115 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.documentmanagement.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import org.apache.fineract.infrastructure.contentstore.data.ContentStoreType; +import org.apache.fineract.infrastructure.contentstore.detector.ContentDetectorManager; +import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService; +import org.apache.fineract.infrastructure.documentmanagement.adapter.EntityImageIdAdapter; +import org.apache.fineract.infrastructure.documentmanagement.data.ImageCreateRequest; +import org.apache.fineract.infrastructure.documentmanagement.domain.Image; +import org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ImageWritePlatformServiceImplTest { + + @Mock + private EntityImageIdAdapter clientImageIdAdapter; + @Mock + private EntityImageIdAdapter staffImageIdAdapter; + @Mock + private ContentStoreService contentStoreService; + @Mock + private ImageRepository imageRepository; + @Mock + private ContentDetectorManager contentDetectorManager; + + private ImageWritePlatformServiceImpl underTest; + + @BeforeEach + void setUp() { + underTest = new ImageWritePlatformServiceImpl(List.of(clientImageIdAdapter, staffImageIdAdapter), contentStoreService, + imageRepository, contentDetectorManager); + + when(contentStoreService.getDelimiter()).thenReturn("/"); + when(contentStoreService.getType()).thenReturn(ContentStoreType.FILE_SYSTEM); + when(contentStoreService.upload(anyString(), any(InputStream.class), anyString())) + .thenAnswer(invocation -> invocation.getArgument(0)); + when(imageRepository.save(any(Image.class))).thenAnswer(invocation -> { + Image image = invocation.getArgument(0); + image.setId(99L); + return image; + }); + } + + @Test + void createImageShouldStoreStaffImageUnderStaffDirectory() { + Long entityId = 7L; + when(staffImageIdAdapter.accept("staff")).thenReturn(true); + when(staffImageIdAdapter.get(entityId)).thenReturn(Optional.empty()); + when(staffImageIdAdapter.set(eq(entityId), anyLong())).thenReturn(Optional.empty()); + when(clientImageIdAdapter.accept(anyString())).thenReturn(false); + + ImageCreateRequest request = ImageCreateRequest.builder().entityType("STAFF").entityId(entityId).fileName("profile.png") + .type("image/png").stream(new ByteArrayInputStream(new byte[] { 1, 2, 3 })).build(); + + var response = underTest.createImage(request); + + ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + verify(contentStoreService).upload(pathCaptor.capture(), any(InputStream.class), eq("image/png")); + assertEquals("images/staff/7/profile.png", pathCaptor.getValue()); + assertEquals(99L, response.getResourceId()); + } + + @Test + void createImageShouldStoreClientImageUnderClientsDirectory() { + Long entityId = 7L; + when(clientImageIdAdapter.accept("clients")).thenReturn(true); + when(clientImageIdAdapter.get(entityId)).thenReturn(Optional.empty()); + when(clientImageIdAdapter.set(eq(entityId), anyLong())).thenReturn(Optional.empty()); + + ImageCreateRequest request = ImageCreateRequest.builder().entityType("CLIENTS").entityId(entityId).fileName("profile.png") + .type("image/png").stream(new ByteArrayInputStream(new byte[] { 1, 2, 3 })).build(); + + var response = underTest.createImage(request); + + ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + verify(contentStoreService).upload(pathCaptor.capture(), any(InputStream.class), eq("image/png")); + assertEquals("images/clients/7/profile.png", pathCaptor.getValue()); + assertEquals(99L, response.getResourceId()); + } +}