FINERACT-2418: Add originators details to external business events

This commit is contained in:
mariiaKraievska
2026-02-06 09:37:36 +01:00
committed by Arnold Gálovics
parent 288038d7bb
commit c57f44d3f0
8 changed files with 714 additions and 0 deletions
@@ -25,6 +25,10 @@ dependencies {
// implementation dependencies are directly used (compiled against) in src/main (and src/test)
//
implementation(project(path: ':fineract-core'))
implementation('org.apache.avro:avro')
implementation(
project(path: ':fineract-avro-schemas')
)
implementation(project(path: ':fineract-loan'))
implementation(
@@ -22,12 +22,23 @@ import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface LoanOriginatorMappingRepository
extends JpaRepository<LoanOriginatorMapping, Long>, JpaSpecificationExecutor<LoanOriginatorMapping> {
List<LoanOriginatorMapping> findByLoanId(Long loanId);
@Query("""
SELECT m FROM LoanOriginatorMapping m
JOIN FETCH m.originator o
LEFT JOIN FETCH o.originatorType
LEFT JOIN FETCH o.channelType
WHERE m.loanId = :loanId
""")
List<LoanOriginatorMapping> findByLoanIdWithOriginatorDetails(@Param("loanId") Long loanId);
boolean existsByLoanId(Long loanId);
boolean existsByOriginatorId(Long originatorId);
@@ -0,0 +1,72 @@
/**
* 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.portfolio.loanorigination.enricher;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.avro.loan.v1.LoanAccountDataV1;
import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
import org.apache.fineract.infrastructure.core.service.DataEnricher;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class LoanAccountDataV1OriginatorEnricher implements DataEnricher<LoanAccountDataV1> {
private final LoanOriginatorMappingRepository loanOriginatorMappingRepository;
private final LoanOriginatorAvroMapper loanOriginatorAvroMapper;
@Override
public boolean isDataTypeSupported(final Class<LoanAccountDataV1> dataType) {
return dataType.isAssignableFrom(LoanAccountDataV1.class);
}
@Override
public void enrich(final LoanAccountDataV1 data) {
if (data == null || data.getId() == null) {
return;
}
final List<LoanOriginatorMapping> mappings = loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(data.getId());
if (mappings == null || mappings.isEmpty()) {
return;
}
final List<OriginatorDetailsV1> originators = new ArrayList<>();
for (LoanOriginatorMapping mapping : mappings) {
final LoanOriginator originator = mapping.getOriginator();
if (originator != null) {
final OriginatorDetailsV1 originatorDetails = loanOriginatorAvroMapper.toAvro(originator);
if (originatorDetails != null) {
originators.add(originatorDetails);
}
}
}
if (!originators.isEmpty()) {
data.setOriginators(originators);
}
}
}
@@ -0,0 +1,70 @@
/**
* 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.portfolio.loanorigination.enricher;
import org.apache.fineract.avro.generic.v1.CodeValueDataV1;
import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@Component
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class LoanOriginatorAvroMapper {
/**
* Converts a LoanOriginator entity to OriginatorDetailsV1 Avro
*/
public OriginatorDetailsV1 toAvro(final LoanOriginator originator) {
if (originator == null) {
return null;
}
final OriginatorDetailsV1.Builder builder = OriginatorDetailsV1.newBuilder();
builder.setId(originator.getId());
builder.setExternalId(originator.getExternalId() != null ? originator.getExternalId().getValue() : null);
builder.setName(originator.getName());
builder.setStatus(originator.getStatus() != null ? originator.getStatus().getValue() : null);
builder.setOriginatorType(mapCodeValue(originator.getOriginatorType()));
builder.setChannelType(mapCodeValue(originator.getChannelType()));
return builder.build();
}
/**
* Converts a CodeValue entity to CodeValueDataV1 Avro
*/
private CodeValueDataV1 mapCodeValue(CodeValue codeValue) {
if (codeValue == null) {
return null;
}
CodeValueDataV1.Builder builder = CodeValueDataV1.newBuilder();
builder.setId(codeValue.getId());
builder.setName(codeValue.getLabel());
builder.setPosition(codeValue.getPosition());
builder.setDescription(codeValue.getDescription());
builder.setActive(codeValue.isActive());
builder.setMandatory(codeValue.isMandatory());
return builder.build();
}
}
@@ -0,0 +1,72 @@
/**
* 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.portfolio.loanorigination.enricher;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
import org.apache.fineract.infrastructure.core.service.DataEnricher;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class LoanTransactionDataV1OriginatorEnricher implements DataEnricher<LoanTransactionDataV1> {
private final LoanOriginatorMappingRepository loanOriginatorMappingRepository;
private final LoanOriginatorAvroMapper loanOriginatorAvroMapper;
@Override
public boolean isDataTypeSupported(final Class<LoanTransactionDataV1> dataType) {
return dataType.isAssignableFrom(LoanTransactionDataV1.class);
}
@Override
public void enrich(final LoanTransactionDataV1 data) {
if (data == null || data.getLoanId() == null) {
return;
}
final List<LoanOriginatorMapping> mappings = loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(data.getLoanId());
if (mappings == null || mappings.isEmpty()) {
return;
}
final List<OriginatorDetailsV1> originators = new ArrayList<>();
for (LoanOriginatorMapping mapping : mappings) {
final LoanOriginator originator = mapping.getOriginator();
if (originator != null) {
final OriginatorDetailsV1 originatorDetails = loanOriginatorAvroMapper.toAvro(originator);
if (originatorDetails != null) {
originators.add(originatorDetails);
}
}
}
if (!originators.isEmpty()) {
data.setOriginators(originators);
}
}
}
@@ -0,0 +1,198 @@
/**
* 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.portfolio.loanorigination.enricher;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import org.apache.fineract.avro.loan.v1.LoanAccountDataV1;
import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
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)
class LoanAccountDataV1OriginatorEnricherTest {
@Mock
private LoanOriginatorMappingRepository loanOriginatorMappingRepository;
@Mock
private LoanOriginatorAvroMapper loanOriginatorAvroMapper;
@InjectMocks
private LoanAccountDataV1OriginatorEnricher enricher;
private LoanAccountDataV1 loanAccountData;
private Long loanId;
@BeforeEach
void setUp() {
loanId = 1L;
loanAccountData = new LoanAccountDataV1();
loanAccountData.setId(loanId);
}
@Test
void testIsDataTypeSupported() {
assertTrue(enricher.isDataTypeSupported(LoanAccountDataV1.class));
}
@Test
void testEnrich_WithOriginators() {
// Given
final LoanOriginator originator = createTestOriginator(1L, "test-originator-1", "Test Originator 1");
final LoanOriginatorMapping mapping = LoanOriginatorMapping.create(loanId, originator);
final List<LoanOriginatorMapping> mappings = List.of(mapping);
final OriginatorDetailsV1 originatorDetails = createOriginatorDetailsV1(1L, "test-originator-1", "Test Originator 1");
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
when(loanOriginatorAvroMapper.toAvro(originator)).thenReturn(originatorDetails);
// When
enricher.enrich(loanAccountData);
// Then
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
verify(loanOriginatorAvroMapper).toAvro(originator);
assertNotNull(loanAccountData.getOriginators());
assertEquals(1, loanAccountData.getOriginators().size());
assertEquals("test-originator-1", loanAccountData.getOriginators().getFirst().getExternalId());
}
@Test
void testEnrich_WithMultipleOriginators() {
// Given
final LoanOriginator originator1 = createTestOriginator(1L, "test-originator-1", "Test Originator 1");
final LoanOriginator originator2 = createTestOriginator(2L, "test-originator-2", "Test Originator 2");
final List<LoanOriginatorMapping> mappings = List.of(LoanOriginatorMapping.create(loanId, originator1),
LoanOriginatorMapping.create(loanId, originator2));
final OriginatorDetailsV1 details1 = createOriginatorDetailsV1(1L, "test-originator-1", "Test Originator 1");
final OriginatorDetailsV1 details2 = createOriginatorDetailsV1(2L, "test-originator-2", "Test Originator 2");
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
when(loanOriginatorAvroMapper.toAvro(originator1)).thenReturn(details1);
when(loanOriginatorAvroMapper.toAvro(originator2)).thenReturn(details2);
// When
enricher.enrich(loanAccountData);
// Then
assertNotNull(loanAccountData.getOriginators());
assertEquals(2, loanAccountData.getOriginators().size());
}
@Test
void testEnrich_NoOriginators() {
// Given
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(Collections.emptyList());
// When
enricher.enrich(loanAccountData);
// Then
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
verify(loanOriginatorAvroMapper, never()).toAvro(any());
assertNull(loanAccountData.getOriginators());
}
@Test
void testEnrich_NullLoanId() {
// Given
loanAccountData.setId(null);
// When
enricher.enrich(loanAccountData);
// Then
verify(loanOriginatorMappingRepository, never()).findByLoanIdWithOriginatorDetails(any());
assertNull(loanAccountData.getOriginators());
}
@Test
void testEnrich_NullData() {
// When
enricher.enrich(null);
// Then
verify(loanOriginatorMappingRepository, never()).findByLoanIdWithOriginatorDetails(any());
}
@Test
void testEnrich_NullMappings() {
// Given
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(null);
// When
enricher.enrich(loanAccountData);
// Then
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
verify(loanOriginatorAvroMapper, never()).toAvro(any());
assertNull(loanAccountData.getOriginators());
}
@Test
void testEnrich_NullOriginatorInMapping() {
// Given
final LoanOriginatorMapping mapping = mock(LoanOriginatorMapping.class);
when(mapping.getOriginator()).thenReturn(null);
final List<LoanOriginatorMapping> mappings = List.of(mapping);
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
// When
enricher.enrich(loanAccountData);
// Then
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
verify(loanOriginatorAvroMapper, never()).toAvro(any());
assertNull(loanAccountData.getOriginators());
}
private LoanOriginator createTestOriginator(final Long id, final String externalId, final String name) {
final LoanOriginator originator = LoanOriginator.create(new ExternalId(externalId), name, LoanOriginatorStatus.ACTIVE, null, null);
originator.setId(id);
return originator;
}
private OriginatorDetailsV1 createOriginatorDetailsV1(final Long id, final String externalId, final String name) {
return OriginatorDetailsV1.newBuilder().setId(id).setExternalId(externalId).setName(name).setStatus("ACTIVE")
.setOriginatorType(null).setChannelType(null).build();
}
}
@@ -0,0 +1,122 @@
/**
* 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.portfolio.loanorigination.enricher;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class LoanOriginatorAvroMapperTest {
private LoanOriginatorAvroMapper mapper;
@BeforeEach
void setUp() {
mapper = new LoanOriginatorAvroMapper();
}
@Test
void testToAvro_WithAllFields() {
// Given
final CodeValue originatorType = createCodeValue(1L, "MERCHANT", "Merchant");
final CodeValue channelType = createCodeValue(2L, "ONLINE", "Online");
final LoanOriginator originator = LoanOriginator.create(new ExternalId("test-external-id"), "Test Originator",
LoanOriginatorStatus.ACTIVE, originatorType, channelType);
originator.setId(100L);
// When
final OriginatorDetailsV1 result = mapper.toAvro(originator);
// Then
assertNotNull(result);
assertEquals(100L, result.getId());
assertEquals("test-external-id", result.getExternalId());
assertEquals("Test Originator", result.getName());
assertEquals("ACTIVE", result.getStatus());
assertNotNull(result.getOriginatorType());
assertEquals(1L, result.getOriginatorType().getId());
assertEquals("MERCHANT", result.getOriginatorType().getName());
assertNotNull(result.getChannelType());
assertEquals(2L, result.getChannelType().getId());
assertEquals("ONLINE", result.getChannelType().getName());
}
@Test
void testToAvro_WithNullFields() {
// Given
final LoanOriginator originator = LoanOriginator.create(new ExternalId("test-external-id"), null, LoanOriginatorStatus.PENDING,
null, null);
originator.setId(200L);
// When
final OriginatorDetailsV1 result = mapper.toAvro(originator);
// Then
assertNotNull(result);
assertEquals(200L, result.getId());
assertEquals("test-external-id", result.getExternalId());
assertNull(result.getName());
assertEquals("PENDING", result.getStatus());
assertNull(result.getOriginatorType());
assertNull(result.getChannelType());
}
@Test
void testToAvro_NullOriginator() {
// When
final OriginatorDetailsV1 result = mapper.toAvro(null);
// Then
assertNull(result);
}
@Test
void testToAvro_NullExternalId() {
// Given
final LoanOriginator originator = LoanOriginator.create(null, "Test", LoanOriginatorStatus.ACTIVE, null, null);
originator.setId(300L);
// When
final OriginatorDetailsV1 result = mapper.toAvro(originator);
// Then
assertNotNull(result);
assertEquals(300L, result.getId());
assertNull(result.getExternalId());
}
private CodeValue createCodeValue(final Long id, final String label, final String description) {
final CodeValue codeValue = new CodeValue();
codeValue.setId(id);
codeValue.setLabel(label);
codeValue.setDescription(description);
codeValue.setPosition(1);
codeValue.setActive(true);
codeValue.setMandatory(false);
return codeValue;
}
}
@@ -0,0 +1,165 @@
/**
* 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.portfolio.loanorigination.enricher;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
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)
class LoanTransactionDataV1OriginatorEnricherTest {
@Mock
private LoanOriginatorMappingRepository loanOriginatorMappingRepository;
@Mock
private LoanOriginatorAvroMapper loanOriginatorAvroMapper;
@InjectMocks
private LoanTransactionDataV1OriginatorEnricher enricher;
private LoanTransactionDataV1 loanTransactionData;
private Long loanId;
@BeforeEach
void setUp() {
loanId = 1L;
loanTransactionData = new LoanTransactionDataV1();
loanTransactionData.setLoanId(loanId);
}
@Test
void testIsDataTypeSupported() {
assertTrue(enricher.isDataTypeSupported(LoanTransactionDataV1.class));
}
@Test
void testEnrich_WithOriginators() {
// Given
final LoanOriginator originator = createTestOriginator(1L, "test-originator-1", "Test Originator 1");
final LoanOriginatorMapping mapping = LoanOriginatorMapping.create(loanId, originator);
final List<LoanOriginatorMapping> mappings = List.of(mapping);
final OriginatorDetailsV1 originatorDetails = createOriginatorDetailsV1(1L, "test-originator-1", "Test Originator 1");
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
when(loanOriginatorAvroMapper.toAvro(originator)).thenReturn(originatorDetails);
// When
enricher.enrich(loanTransactionData);
// Then
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
verify(loanOriginatorAvroMapper).toAvro(originator);
assertNotNull(loanTransactionData.getOriginators());
assertEquals(1, loanTransactionData.getOriginators().size());
assertEquals("test-originator-1", loanTransactionData.getOriginators().getFirst().getExternalId());
}
@Test
void testEnrich_WithMultipleOriginators() {
// Given
final LoanOriginator originator1 = createTestOriginator(1L, "test-originator-1", "Test Originator 1");
final LoanOriginator originator2 = createTestOriginator(2L, "test-originator-2", "Test Originator 2");
final List<LoanOriginatorMapping> mappings = List.of(LoanOriginatorMapping.create(loanId, originator1),
LoanOriginatorMapping.create(loanId, originator2));
final OriginatorDetailsV1 details1 = createOriginatorDetailsV1(1L, "test-originator-1", "Test Originator 1");
final OriginatorDetailsV1 details2 = createOriginatorDetailsV1(2L, "test-originator-2", "Test Originator 2");
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
when(loanOriginatorAvroMapper.toAvro(originator1)).thenReturn(details1);
when(loanOriginatorAvroMapper.toAvro(originator2)).thenReturn(details2);
// When
enricher.enrich(loanTransactionData);
// Then
assertNotNull(loanTransactionData.getOriginators());
assertEquals(2, loanTransactionData.getOriginators().size());
}
@Test
void testEnrich_NoOriginators() {
// Given
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(Collections.emptyList());
// When
enricher.enrich(loanTransactionData);
// Then
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
verify(loanOriginatorAvroMapper, never()).toAvro(any());
assertNull(loanTransactionData.getOriginators());
}
@Test
void testEnrich_NullLoanId() {
// Given
loanTransactionData.setLoanId(null);
// When
enricher.enrich(loanTransactionData);
// Then
verify(loanOriginatorMappingRepository, never()).findByLoanIdWithOriginatorDetails(any());
assertNull(loanTransactionData.getOriginators());
}
@Test
void testEnrich_NullData() {
// When
enricher.enrich(null);
// Then
verify(loanOriginatorMappingRepository, never()).findByLoanIdWithOriginatorDetails(any());
}
private LoanOriginator createTestOriginator(final Long id, final String externalId, final String name) {
final LoanOriginator originator = LoanOriginator.create(new ExternalId(externalId), name, LoanOriginatorStatus.ACTIVE, null, null);
originator.setId(id);
return originator;
}
private OriginatorDetailsV1 createOriginatorDetailsV1(final Long id, final String externalId, final String name) {
return OriginatorDetailsV1.newBuilder().setId(id).setExternalId(externalId).setName(name).setStatus("ACTIVE")
.setOriginatorType(null).setChannelType(null).build();
}
}