diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc index 273c22e90..3fbe75e51 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc @@ -823,6 +823,17 @@ "null", "boolean" ] + }, + { + "default": null, + "name": "customData", + "type": [ + "null", + { + "values": "bytes", + "type": "map" + } + ] } ] } diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc index 1503ddafd..d11de9746 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc @@ -244,6 +244,17 @@ "null", "string" ] + }, + { + "default": null, + "name": "customData", + "type": [ + "null", + { + "values": "bytes", + "type": "map" + } + ] } ] } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/ExternalEventCustomDataSerializer.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/ExternalEventCustomDataSerializer.java new file mode 100644 index 000000000..4b2570bc2 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/ExternalEventCustomDataSerializer.java @@ -0,0 +1,42 @@ +/** + * 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.event.external.service.serialization.serializer; + +import java.nio.ByteBuffer; +import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; + +public interface ExternalEventCustomDataSerializer> { + + /** + * Serializes the custom data from a business event into a byte buffer + * + * @param event + * The business event containing the data to serialize + * @return The serialized data as a ByteBuffer + */ + ByteBuffer serialize(T event); + + /** + * Returns the key used to identify this serializer's data in the custom data map + * + * @return The key string + */ + String key(); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanAccountDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanAccountDataMapper.java index d55a124f7..531f1b355 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanAccountDataMapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanAccountDataMapper.java @@ -37,6 +37,7 @@ public interface LoanAccountDataMapper { @Mapping(target = "settlementDate", ignore = true) @Mapping(target = "purchasePriceRatio", ignore = true) @Mapping(target = "delinquent.installmentDelinquencyBuckets", ignore = true) + @Mapping(target = "customData", ignore = true) LoanAccountDataV1 map(LoanAccountData source); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java index 412c96e04..cf6642cbc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java @@ -30,5 +30,6 @@ public interface LoanTransactionDataMapper { // unpaidCharges are calculated and set explicitly based on if needed (only for charge-off transaction yet) @Mapping(target = "unpaidCharges", ignore = true) @Mapping(target = "externalOwnerId", ignore = true) + @Mapping(target = "customData", ignore = true) LoanTransactionDataV1 map(LoanTransactionData source); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java index ef8588c6c..43ea187f6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java @@ -18,8 +18,11 @@ */ package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan; +import java.nio.ByteBuffer; import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.apache.avro.generic.GenericContainer; import org.apache.commons.collections4.CollectionUtils; @@ -30,6 +33,7 @@ import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent; import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanAccountDataMapper; import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer; +import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.ExternalEventCustomDataSerializer; import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.data.CollectionData; @@ -56,6 +60,7 @@ public class LoanBusinessEventSerializer implements BusinessEventSerializer { private final LoanSummaryBalancesRepository loanSummaryBalancesRepository; @Lazy private final LoanSummaryProviderDelegate loanSummaryProviderDelegate; + private final List> externalEventCustomDataSerializers; @Override public boolean canSerialize(BusinessEvent event) { @@ -98,8 +103,11 @@ public class LoanBusinessEventSerializer implements BusinessEventSerializer { data.setLoanTermVariations(activeLoanTermVariations.stream().map(LoanTermVariations::toData).toList()); } - LoanAccountDataV1 result = mapper.map(data); + final LoanAccountDataV1 result = mapper.map(data); result.getDelinquent().setInstallmentDelinquencyBuckets(installmentsDelinquencyData); + + result.setCustomData(collectCustomData(event)); + return result; } @@ -107,4 +115,10 @@ public class LoanBusinessEventSerializer implements BusinessEventSerializer { public Class getSupportedSchema() { return LoanAccountDataV1.class; } + + private Map collectCustomData(final LoanBusinessEvent event) { + return externalEventCustomDataSerializers.stream().collect(Collectors.toMap(ExternalEventCustomDataSerializer::key, + serializer -> serializer.serialize(event), (existing, replacement) -> replacement)); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeOffBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeOffBusinessEventSerializer.java index 2b8b70853..7a65d0972 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeOffBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeOffBusinessEventSerializer.java @@ -19,7 +19,6 @@ package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan; import java.util.List; -import org.apache.avro.generic.GenericContainer; import org.apache.fineract.avro.generator.ByteBufferSerializable; import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1; import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; @@ -27,6 +26,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanTransactionDataMapper; import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.UnpaidChargeDataMapper; +import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.ExternalEventCustomDataSerializer; import org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService; @@ -44,8 +44,9 @@ public class LoanChargeOffBusinessEventSerializer extends LoanTransactionBusines public LoanChargeOffBusinessEventSerializer(LoanReadPlatformService loanReadPlatformService, LoanTransactionDataMapper loanTransactionMapper, LoanChargePaidByReadService loanChargePaidByReadService, - UnpaidChargeDataMapper unpaidChargeDataMapper, LoanTransactionRepository loanTransactionRepository) { - super(loanReadPlatformService, loanTransactionMapper, loanChargePaidByReadService); + UnpaidChargeDataMapper unpaidChargeDataMapper, LoanTransactionRepository loanTransactionRepository, + List> externalEventCustomDataSerializers) { + super(loanReadPlatformService, loanTransactionMapper, loanChargePaidByReadService, externalEventCustomDataSerializers); this.unpaidChargeDataMapper = unpaidChargeDataMapper; this.loanTransactionRepository = loanTransactionRepository; } @@ -61,11 +62,8 @@ public class LoanChargeOffBusinessEventSerializer extends LoanTransactionBusines LoanTransactionBusinessEvent event = (LoanTransactionBusinessEvent) rawEvent; List unpaidChargeDataList = loanTransactionRepository.fetchTotalUnpaidChargesForLoan(event.get().getLoan()); transactionDataV1.setUnpaidCharges(unpaidChargeDataMapper.map(unpaidChargeDataList)); + transactionDataV1.setCustomData(collectCustomData(event)); return transactionDataV1; } - @Override - public Class getSupportedSchema() { - return LoanTransactionDataV1.class; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java index 9b5a1aea5..0bf852aa9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java @@ -18,6 +18,10 @@ */ package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.apache.avro.generic.GenericContainer; import org.apache.fineract.avro.generator.ByteBufferSerializable; @@ -26,6 +30,7 @@ import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanTransactionDataMapper; import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer; +import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.ExternalEventCustomDataSerializer; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; @@ -38,6 +43,7 @@ public class LoanTransactionBusinessEventSerializer implements BusinessEventSeri private final LoanReadPlatformService service; private final LoanTransactionDataMapper loanTransactionMapper; private final LoanChargePaidByReadService loanChargePaidByReadService; + private final List> externalEventCustomDataSerializers; @Override public boolean canSerialize(BusinessEvent event) { @@ -51,11 +57,21 @@ public class LoanTransactionBusinessEventSerializer implements BusinessEventSeri Long loanTransactionId = event.get().getId(); LoanTransactionData transactionData = service.retrieveLoanTransaction(loanId, loanTransactionId); transactionData.setLoanChargePaidByList(loanChargePaidByReadService.fetchLoanChargesPaidByDataTransactionId(loanTransactionId)); - return loanTransactionMapper.map(transactionData); + + final LoanTransactionDataV1 result = loanTransactionMapper.map(transactionData); + result.setCustomData(collectCustomData(event)); + + return result; } @Override public Class getSupportedSchema() { return LoanTransactionDataV1.class; } + + protected Map collectCustomData(final LoanTransactionBusinessEvent event) { + return externalEventCustomDataSerializers.stream().collect(Collectors.toMap(ExternalEventCustomDataSerializer::key, + serializer -> serializer.serialize(event), (existing, replacement) -> replacement)); + } + } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializerTest.java new file mode 100644 index 000000000..09cfe6d27 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializerTest.java @@ -0,0 +1,173 @@ +/** + * 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.event.external.service.serialization.serializer.loan; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.nio.ByteBuffer; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.fineract.avro.loan.v1.CollectionDataV1; +import org.apache.fineract.avro.loan.v1.LoanAccountDataV1; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent; +import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanAccountDataMapper; +import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.ExternalEventCustomDataSerializer; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.data.CollectionData; +import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; +import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; +import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryDataProvider; +import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryProviderDelegate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@MockitoSettings(strictness = Strictness.LENIENT) +@ExtendWith(MockitoExtension.class) +class LoanBusinessEventSerializerTest { + + @Mock + private LoanReadPlatformService loanReadPlatformService; + @Mock + private LoanAccountDataMapper mapper; + @Mock + private LoanChargeReadPlatformService loanChargeReadPlatformService; + @Mock + private DelinquencyReadPlatformService delinquencyReadPlatformService; + @Mock + private LoanInstallmentLevelDelinquencyEventProducer installmentLevelDelinquencyEventProducer; + @Mock + private LoanSummaryBalancesRepository loanSummaryBalancesRepository; + @Mock + private LoanSummaryProviderDelegate loanSummaryProviderDelegate; + @Mock + private LoanSummaryDataProvider loanSummaryDataProvider; + + private LoanBusinessEventSerializer loanBusinessEventSerializer; + + @BeforeEach + void setUp() { + final List> externalEventCustomDataSerializers = List + .of(new ExternalEventCustomDataSerializer<>() { + + @Override + public ByteBuffer serialize(final LoanBusinessEvent event) { + return ByteBuffer.wrap("test_data_for_loan".getBytes(UTF_8)); + } + + @Override + public String key() { + return "test_key_1"; + } + }, new ExternalEventCustomDataSerializer<>() { + + @Override + public ByteBuffer serialize(final LoanBusinessEvent event) { + return ByteBuffer.wrap("test_data_for_loan_1".getBytes(UTF_8)); + } + + @Override + public String key() { + return "test_key_1"; + } + }, new ExternalEventCustomDataSerializer<>() { + + @Override + public ByteBuffer serialize(final LoanBusinessEvent event) { + return ByteBuffer.wrap("test_data_for_loan_2".getBytes(UTF_8)); + } + + @Override + public String key() { + return "test_key_2"; + } + }); + + loanBusinessEventSerializer = new LoanBusinessEventSerializer(loanReadPlatformService, mapper, loanChargeReadPlatformService, + delinquencyReadPlatformService, installmentLevelDelinquencyEventProducer, loanSummaryBalancesRepository, + loanSummaryProviderDelegate, externalEventCustomDataSerializers); + } + + @Test + void testLoanCustomDataSerialization() { + final long loanId = 1L; + final Loan loan = mock(Loan.class); + final LoanBusinessEvent event = mock(LoanBusinessEvent.class); + final LoanAccountData loanAccountData = mock(LoanAccountData.class); + final CollectionData collectionData = mock(CollectionData.class); + final CurrencyData currencyData = mock(CurrencyData.class); + final LoanSummary loanSummary = mock(LoanSummary.class); + final LoanSummaryData loanSummaryData = mock(LoanSummaryData.class); + + when(loan.getId()).thenReturn(loanId); + when(event.get()).thenReturn(loan); + when(loan.getActiveLoanTermVariations()).thenReturn(new ArrayList<>()); + when(loan.getSummary()).thenReturn(loanSummary); + + when(loanAccountData.getCurrency()).thenReturn(currencyData); + when(loanAccountData.getTransactionProcessingStrategyCode()).thenReturn("strategy-code"); + when(loanAccountData.getSummary()).thenReturn(loanSummaryData); + when(loanReadPlatformService.retrieveOne(loanId)).thenReturn(loanAccountData); + when(loanReadPlatformService.fetchRepaymentScheduleData(loanAccountData)).thenReturn(loanAccountData); + + when(delinquencyReadPlatformService.calculateLoanCollectionData(anyLong())).thenReturn(collectionData); + when(installmentLevelDelinquencyEventProducer.calculateInstallmentLevelDelinquencyData(any(), any())).thenReturn(new ArrayList<>()); + + when(loanSummaryProviderDelegate.resolveLoanSummaryDataProvider(any())).thenReturn(loanSummaryDataProvider); + when(loanSummaryDataProvider.withOnlyCurrencyData(any())).thenReturn(loanSummaryData); + + final CollectionDataV1 collectionDataV1 = CollectionDataV1.newBuilder() + .setDelinquentDate(LocalDate.now(ZoneId.systemDefault()).toString()).setDelinquentDays(0) + .setInstallmentDelinquencyBuckets(new ArrayList<>()).build(); + + final LoanAccountDataV1 expectedAvroData = LoanAccountDataV1.newBuilder().setId(loanId).setCustomData(new HashMap<>()) + .setDelinquent(collectionDataV1).build(); + + when(mapper.map(any(LoanAccountData.class))).thenReturn(expectedAvroData); + + final LoanAccountDataV1 result = (LoanAccountDataV1) loanBusinessEventSerializer.toAvroDTO(event); + + assertNotNull(result); + assertNotNull(result.getCustomData()); + final Map customData = result.getCustomData(); + assertEquals("test_data_for_loan_1", new String(customData.get("test_key_1").array(), UTF_8)); + assertEquals("test_data_for_loan_2", new String(customData.get("test_key_2").array(), UTF_8)); + } + +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializerTest.java new file mode 100644 index 000000000..0ea71f9cd --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializerTest.java @@ -0,0 +1,136 @@ +/** + * 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.event.external.service.serialization.serializer.loan; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent; +import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanTransactionDataMapper; +import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.ExternalEventCustomDataSerializer; +import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService; +import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@MockitoSettings(strictness = Strictness.LENIENT) +@ExtendWith(MockitoExtension.class) +class LoanTransactionBusinessEventSerializerTest { + + @Mock + private LoanReadPlatformService loanReadPlatformService; + @Mock + private LoanTransactionDataMapper loanTransactionMapper; + @Mock + private LoanChargePaidByReadService loanChargePaidByReadService; + + private LoanTransactionBusinessEventSerializer serializer; + + @BeforeEach + void setUp() { + final List> externalEventCustomDataSerializers = List + .of(new ExternalEventCustomDataSerializer<>() { + + @Override + public ByteBuffer serialize(final LoanTransactionBusinessEvent event) { + return ByteBuffer.wrap("test_data_for_loan_transaction".getBytes(UTF_8)); + } + + @Override + public String key() { + return "test_key_1"; + } + }, new ExternalEventCustomDataSerializer<>() { + + @Override + public ByteBuffer serialize(final LoanTransactionBusinessEvent event) { + return ByteBuffer.wrap("test_data_for_loan_transaction_1".getBytes(UTF_8)); + } + + @Override + public String key() { + return "test_key_1"; + } + }, new ExternalEventCustomDataSerializer<>() { + + @Override + public ByteBuffer serialize(final LoanTransactionBusinessEvent event) { + return ByteBuffer.wrap("test_data_for_loan_transaction_2".getBytes(UTF_8)); + } + + @Override + public String key() { + return "test_key_2"; + } + }); + serializer = new LoanTransactionBusinessEventSerializer(loanReadPlatformService, loanTransactionMapper, loanChargePaidByReadService, + externalEventCustomDataSerializers); + } + + @Test + void testTransactionCustomDataSerialization() { + final long loanId = 1; + final long transactionId = 2; + + final Loan loan = mock(Loan.class); + final LoanTransaction loanTransaction = mock(LoanTransaction.class); + final LoanTransactionBusinessEvent event = mock(LoanTransactionBusinessEvent.class); + final LoanTransactionData transactionData = mock(LoanTransactionData.class); + + when(loan.getId()).thenReturn(loanId); + when(loanTransaction.getId()).thenReturn(transactionId); + when(loanTransaction.getLoan()).thenReturn(loan); + when(event.get()).thenReturn(loanTransaction); + + when(loanReadPlatformService.retrieveLoanTransaction(loanId, transactionId)).thenReturn(transactionData); + when(loanChargePaidByReadService.fetchLoanChargesPaidByDataTransactionId(anyLong())).thenReturn(new ArrayList<>()); + + final LoanTransactionDataV1 expectedAvroData = LoanTransactionDataV1.newBuilder().setId(transactionId).setLoanId(loanId) + .setCustomData(new HashMap<>()).build(); + when(loanTransactionMapper.map(any(LoanTransactionData.class))).thenReturn(expectedAvroData); + + final LoanTransactionDataV1 result = (LoanTransactionDataV1) serializer.toAvroDTO(event); + + assertNotNull(result); + assertNotNull(result.getCustomData()); + final Map customData = result.getCustomData(); + assertEquals("test_data_for_loan_transaction_1", new String(customData.get("test_key_1").array(), UTF_8)); + assertEquals("test_data_for_loan_transaction_2", new String(customData.get("test_key_2").array(), UTF_8)); + } + +}