This is an automated email from the ASF dual-hosted git repository.
bagrijp pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 8b379695d FINERACT-1972 Custom Snapshot Event Triggered by COB
8b379695d is described below
commit 8b379695d7c60871dbfbc8ff6bc90b324100dd0f
Author: Peter Bagrij <[email protected]>
AuthorDate: Wed Jan 17 08:05:30 2024 +0100
FINERACT-1972 Custom Snapshot Event Triggered by COB
---
.../infrastructure/core/boot/FineractProfiles.java | 2 +
.../api/InternalExternalEventsApiResource.java | 52 ++--
.../repository/ExternalEventRepository.java | 3 +-
.../service/InternalExternalEventService.java | 147 ++++++++++
.../service/validation/ExternalEventDTO.java} | 24 +-
.../LoanAccountCustomSnapshotBusinessEvent.java | 18 +-
.../tenant/module/loan/module-changelog-master.xml | 1 +
...1014_add_loan_account_custom_snapshot_event.xml | 33 +++
.../fineract/cob/api/InternalCOBApiResource.java | 3 +-
.../api/InternalLoanAccountLockApiResource.java | 3 +-
.../cob/loan/CheckDueInstallmentsBusinessStep.java | 69 +++++
.../instancemode/api/InstanceModeApiResource.java | 3 +-
.../s3/LocalstackS3ClientCustomizer.java | 3 +-
.../api/InternalClientInformationApiResource.java | 3 +-
.../api/InternalLoanInformationApiResource.java | 3 +-
.../loan/CheckDueInstallmentsBusinessStepTest.java | 161 ++++++++++
...nalEventConfigurationValidationServiceTest.java | 4 +-
.../integrationtests/BaseLoanIntegrationTest.java | 8 +-
.../CustomSnapshotEventIntegrationTest.java | 326 +++++++++++++++++++++
.../common/ExternalEventConfigurationHelper.java | 5 +
.../fineract/integrationtests/common/Utils.java | 3 +
.../common/externalevents/ExternalEventHelper.java | 92 ++++++
.../externalevents/ExternalEventsExtension.java | 88 ++++++
.../integrationtests/common/loans/CobHelper.java | 9 +-
24 files changed, 1010 insertions(+), 53 deletions(-)
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
index 6d0757021..97aca9634 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
@@ -23,5 +23,7 @@ public final class FineractProfiles {
public static final String LIQUIBASE_ONLY = "liquibase-only";
public static final String DIAGNOSTICS = "diagnostics";
+ public static final String TEST = "test";
+
private FineractProfiles() {}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/api/InternalExternalEventsApiResource.java
similarity index 52%
copy from
fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
copy to
fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/api/InternalExternalEventsApiResource.java
index 7402955bc..31b84f99d 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/api/InternalExternalEventsApiResource.java
@@ -16,43 +16,36 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.cob.api;
+package org.apache.fineract.infrastructure.event.external.api;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
-import jakarta.ws.rs.core.UriInfo;
-import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.apache.fineract.cob.data.LoanCOBPartition;
-import org.apache.fineract.cob.loan.LoanCOBConstant;
-import org.apache.fineract.cob.loan.RetrieveLoanIdService;
-import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
-import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
-import
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
-import
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
-import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
+import
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import
org.apache.fineract.infrastructure.event.external.service.InternalExternalEventService;
+import
org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
-@Profile("test")
+@Profile(FineractProfiles.TEST)
@Component
-@Path("/v1/internal/cob")
+@Path("/v1/internal/externalevents")
@RequiredArgsConstructor
@Slf4j
-public class InternalCOBApiResource implements InitializingBean {
+public class InternalExternalEventsApiResource implements InitializingBean {
- private final RetrieveLoanIdService retrieveLoanIdService;
- private final ApiRequestParameterHelper apiRequestParameterHelper;
- private final ToApiJsonSerializer<List> toApiJsonSerializerForList;
+ private final InternalExternalEventService internalExternalEventService;
+ private final DefaultToApiJsonSerializer<List<ExternalEventDTO>>
jsonSerializer;
@Override
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
@@ -69,14 +62,19 @@ public class InternalCOBApiResource implements
InitializingBean {
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
- @Path("partitions/{partitionSize}")
- public String getCobPartitions(@Context final UriInfo uriInfo,
@PathParam("partitionSize") int partitionSize) {
- LocalDate businessDate =
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.BUSINESS_DATE);
- log.info("RetrieveLoanCOBPartitions is called with partitionSize {}
for {}", partitionSize, businessDate);
- List<LoanCOBPartition> loanCOBPartitions =
retrieveLoanIdService.retrieveLoanCOBPartitions(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND,
- businessDate, false, partitionSize);
- final ApiRequestJsonSerializationSettings settings =
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
- return toApiJsonSerializerForList.serialize(settings,
loanCOBPartitions);
+ public String getAllExternalEvents(@QueryParam("idempotencyKey") final
String idempotencyKey, @QueryParam("type") final String type,
+ @QueryParam("category") final String category,
@QueryParam("aggregateRootId") final Long aggregateRootId) {
+ log.debug("getAllExternalEvents called with params idempotencyKey:{},
type:{}, category:{}, aggregateRootId:{} ", idempotencyKey,
+ type, category, aggregateRootId);
+ List<ExternalEventDTO> allExternalEvents =
internalExternalEventService.getAllExternalEvents(idempotencyKey, type,
category,
+ aggregateRootId);
+ return jsonSerializer.serialize(allExternalEvents);
+ }
+
+ @DELETE
+ public void deleteAllExternalEvents() {
+ log.debug("deleteAllExternalEvents called");
+ internalExternalEventService.deleteAllExternalEvents();
}
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/repository/ExternalEventRepository.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/repository/ExternalEventRepository.java
index 474384d2a..9f7ccbddc 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/repository/ExternalEventRepository.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/repository/ExternalEventRepository.java
@@ -26,11 +26,12 @@ import
org.apache.fineract.infrastructure.event.external.repository.domain.Exter
import
org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventView;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
-public interface ExternalEventRepository extends JpaRepository<ExternalEvent,
Long> {
+public interface ExternalEventRepository extends JpaRepository<ExternalEvent,
Long>, JpaSpecificationExecutor<ExternalEvent> {
List<ExternalEventView> findByStatusOrderById(ExternalEventStatus status,
Pageable batchSize);
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/InternalExternalEventService.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/InternalExternalEventService.java
new file mode 100644
index 000000000..b044b3d33
--- /dev/null
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/InternalExternalEventService.java
@@ -0,0 +1,147 @@
+/**
+ * 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;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.avro.BulkMessageItemV1;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
+import
org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository;
+import
org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEvent;
+import
org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+
+@Service
+@Profile(FineractProfiles.TEST)
+@Slf4j
+@AllArgsConstructor
+public class InternalExternalEventService {
+
+ private final ExternalEventRepository externalEventRepository;
+
+ public void deleteAllExternalEvents() {
+ externalEventRepository.deleteAll();
+ }
+
+ public List<ExternalEventDTO> getAllExternalEvents(String idempotencyKey,
String type, String category, Long aggregateRootId) {
+ List<Specification<ExternalEvent>> specifications = new ArrayList<>();
+
+ if (StringUtils.isNotEmpty(idempotencyKey)) {
+ specifications.add(hasIdempotencyKey(idempotencyKey));
+ }
+
+ if (StringUtils.isNotEmpty(type)) {
+ specifications.add(hasType(type));
+ }
+
+ if (StringUtils.isNotEmpty(category)) {
+ specifications.add(hasCategory(category));
+ }
+
+ if (aggregateRootId != null) {
+ specifications.add(hasAggregateRootId(aggregateRootId));
+ }
+
+ Specification<ExternalEvent> reducedSpecification =
specifications.stream().reduce(Specification::and)
+ .orElse((Specification<ExternalEvent>) (root, query,
criteriaBuilder) -> null);
+ List<ExternalEvent> externalEvents =
externalEventRepository.findAll(reducedSpecification);
+
+ try {
+ return convertToReadableFormat(externalEvents);
+ } catch (ClassNotFoundException | NoSuchMethodException |
InvocationTargetException | IllegalAccessException
+ | JsonProcessingException e) {
+ throw new RuntimeException("Error while converting external events
to readable format", e);
+ }
+ }
+
+ private Specification<ExternalEvent> hasIdempotencyKey(String
idempotencyKey) {
+ return (root, query, cb) -> cb.equal(root.get("idempotencyKey"),
idempotencyKey);
+ }
+
+ private Specification<ExternalEvent> hasType(String type) {
+ return (root, query, cb) -> cb.equal(root.get("type"), type);
+ }
+
+ private Specification<ExternalEvent> hasCategory(String category) {
+ return (root, query, cb) -> cb.equal(root.get("category"), category);
+ }
+
+ private Specification<ExternalEvent> hasAggregateRootId(Long
aggregateRootId) {
+ return (root, query, cb) -> cb.equal(root.get("aggregateRootId"),
aggregateRootId);
+ }
+
+ private List<ExternalEventDTO> convertToReadableFormat(List<ExternalEvent>
externalEvents) throws ClassNotFoundException,
+ NoSuchMethodException, InvocationTargetException,
IllegalAccessException, JsonProcessingException {
+ List<ExternalEventDTO> eventMessages = new ArrayList<>();
+ for (ExternalEvent externalEvent : externalEvents) {
+ Class<?> payLoadClass = Class.forName(externalEvent.getSchema());
+ ByteBuffer byteBuffer = ByteBuffer.wrap(externalEvent.getData());
+ Method method = payLoadClass.getMethod("fromByteBuffer",
ByteBuffer.class);
+ Object payLoad = method.invoke(null, byteBuffer);
+ if (externalEvent.getType().equalsIgnoreCase("BulkBusinessEvent"))
{
+ Method methodToGetDatas =
payLoad.getClass().getMethod("getDatas", (Class<?>) null);
+ List<BulkMessageItemV1> bulkMessages =
(List<BulkMessageItemV1>) methodToGetDatas.invoke(payLoad);
+ StringBuilder bulkMessagePayload = new StringBuilder();
+ for (BulkMessageItemV1 bulkMessage : bulkMessages) {
+ ExternalEventDTO bulkMessageData =
retrieveBulkMessage(bulkMessage, externalEvent);
+ bulkMessagePayload.append(bulkMessageData);
+ bulkMessagePayload.append(System.lineSeparator());
+ }
+ eventMessages.add(new ExternalEventDTO(externalEvent.getId(),
externalEvent.getType(), externalEvent.getCategory(),
+ externalEvent.getCreatedAt(),
toJsonMap(bulkMessagePayload.toString()), externalEvent.getBusinessDate(),
+ externalEvent.getSchema(),
externalEvent.getAggregateRootId()));
+
+ } else {
+ eventMessages.add(new ExternalEventDTO(externalEvent.getId(),
externalEvent.getType(), externalEvent.getCategory(),
+ externalEvent.getCreatedAt(),
toJsonMap(payLoad.toString()), externalEvent.getBusinessDate(),
+ externalEvent.getSchema(),
externalEvent.getAggregateRootId()));
+ }
+ }
+
+ return eventMessages;
+ }
+
+ private ExternalEventDTO retrieveBulkMessage(BulkMessageItemV1
messageItem, ExternalEvent externalEvent) throws ClassNotFoundException,
+ InvocationTargetException, IllegalAccessException,
NoSuchMethodException, JsonProcessingException {
+ Class<?> messageBulkMessagePayLoad =
Class.forName(messageItem.getDataschema());
+ Method methodForPayLoad =
messageBulkMessagePayLoad.getMethod("fromByteBuffer", ByteBuffer.class);
+ Object payLoadBulkItem = methodForPayLoad.invoke(null,
messageItem.getData());
+ return new ExternalEventDTO((long) messageItem.getId(),
messageItem.getType(), messageItem.getCategory(),
+ externalEvent.getCreatedAt(),
toJsonMap(payLoadBulkItem.toString()), externalEvent.getBusinessDate(),
+ externalEvent.getSchema(), externalEvent.getAggregateRootId());
+ }
+
+ private Map<String, Object> toJsonMap(String json) throws
JsonProcessingException {
+ ObjectMapper objectMapper = new ObjectMapper();
+ return objectMapper.readValue(json, new TypeReference<>() {});
+ }
+
+}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventDTO.java
similarity index 56%
copy from
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
copy to
fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventDTO.java
index 6d0757021..dec70373e 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventDTO.java
@@ -16,12 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.infrastructure.core.boot;
+package org.apache.fineract.infrastructure.event.external.service.validation;
-public final class FineractProfiles {
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
- public static final String LIQUIBASE_ONLY = "liquibase-only";
- public static final String DIAGNOSTICS = "diagnostics";
+@Getter
+@AllArgsConstructor
+@ToString
+public class ExternalEventDTO {
- private FineractProfiles() {}
+ private final Long eventId;
+ private final String type;
+ private final String category;
+ private final OffsetDateTime createdAt;
+ private final Map<String, Object> payLoad;
+ private final LocalDate businessDate;
+ private final String schema;
+ private final Long aggregateRootId;
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanAccountCustomSnapshotBusinessEvent.java
similarity index 63%
copy from
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
copy to
fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanAccountCustomSnapshotBusinessEvent.java
index 6d0757021..628429671 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanAccountCustomSnapshotBusinessEvent.java
@@ -16,12 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.infrastructure.core.boot;
+package org.apache.fineract.infrastructure.event.business.domain.loan;
-public final class FineractProfiles {
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
- public static final String LIQUIBASE_ONLY = "liquibase-only";
- public static final String DIAGNOSTICS = "diagnostics";
+public class LoanAccountCustomSnapshotBusinessEvent extends LoanBusinessEvent {
- private FineractProfiles() {}
+ private static final String TYPE =
"LoanAccountCustomSnapshotBusinessEvent";
+
+ public LoanAccountCustomSnapshotBusinessEvent(Loan value) {
+ super(value);
+ }
+
+ @Override
+ public String getType() {
+ return TYPE;
+ }
}
diff --git
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
index b08ead78b..4d5fb3f2b 100644
---
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
+++
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
@@ -36,4 +36,5 @@
<include relativeToChangelogFile="true"
file="parts/1011_add_delinquency_actions_table.xml"/>
<include relativeToChangelogFile="true"
file="parts/1012_introduce_loan_schedule_processing_type_configuration.xml"/>
<include relativeToChangelogFile="true"
file="parts/1013_add_loan_account_delinquency_pause_changed_event.xml"/>
+ <include relativeToChangelogFile="true"
file="parts/1014_add_loan_account_custom_snapshot_event.xml"/>
</databaseChangeLog>
diff --git
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1014_add_loan_account_custom_snapshot_event.xml
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1014_add_loan_account_custom_snapshot_event.xml
new file mode 100644
index 000000000..4c1ad18b9
--- /dev/null
+++
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1014_add_loan_account_custom_snapshot_event.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+
+ <changeSet author="fineract" id="1">
+ <insert tableName="m_external_event_configuration">
+ <column name="type"
value="LoanAccountCustomSnapshotBusinessEvent"/>
+ <column name="enabled" valueBoolean="false"/>
+ </insert>
+ </changeSet>
+
+</databaseChangeLog>
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
index 7402955bc..c767a5479 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
@@ -36,6 +36,7 @@ import org.apache.fineract.cob.loan.LoanCOBConstant;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
@@ -43,7 +44,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
-@Profile("test")
+@Profile(FineractProfiles.TEST)
@Component
@Path("/v1/internal/cob")
@RequiredArgsConstructor
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalLoanAccountLockApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalLoanAccountLockApiResource.java
index 9d5d731d3..a92571978 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalLoanAccountLockApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalLoanAccountLockApiResource.java
@@ -35,13 +35,14 @@ import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
-@Profile("test")
+@Profile(FineractProfiles.TEST)
@Component
@Path("/v1/internal/loans")
@RequiredArgsConstructor
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckDueInstallmentsBusinessStep.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckDueInstallmentsBusinessStep.java
new file mode 100644
index 000000000..fcce7307d
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckDueInstallmentsBusinessStep.java
@@ -0,0 +1,69 @@
+/**
+ * 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.cob.loan;
+
+import java.time.LocalDate;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import
org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountCustomSnapshotBusinessEvent;
+import
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class CheckDueInstallmentsBusinessStep implements LoanCOBBusinessStep {
+
+ private final BusinessEventNotifierService businessEventNotifierService;
+
+ @Override
+ public Loan execute(Loan loan) {
+ log.debug("start processing custom snapshot event trigger business
step loan for loan with id [{}]", loan.getId());
+
+ if (loan.getRepaymentScheduleInstallments() != null &&
loan.getRepaymentScheduleInstallments().size() > 0) {
+ final LocalDate currentDate = DateUtils.getBusinessLocalDate();
+ boolean shouldPostCustomSnapshotBusinessEvent = false;
+ for (int i = 0; i <
loan.getRepaymentScheduleInstallments().size(); i++) {
+ if
(loan.getRepaymentScheduleInstallments().get(i).getDueDate().equals(currentDate)
+ &&
loan.getRepaymentScheduleInstallments().get(i).isNotFullyPaidOff()) {
+ shouldPostCustomSnapshotBusinessEvent = true;
+ }
+ }
+ if (shouldPostCustomSnapshotBusinessEvent) {
+ businessEventNotifierService.notifyPostBusinessEvent(new
LoanAccountCustomSnapshotBusinessEvent(loan));
+ }
+ }
+
+ log.debug("end processing custom snapshot event trigger business step
for loan with id [{}]", loan.getId());
+ return loan;
+ }
+
+ @Override
+ public String getEnumStyledName() {
+ return "CHECK_DUE_INSTALLMENTS";
+ }
+
+ @Override
+ public String getHumanReadableName() {
+ return "Check Due Installments";
+ }
+
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/api/InstanceModeApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/api/InstanceModeApiResource.java
index 215b75463..837638f63 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/api/InstanceModeApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/api/InstanceModeApiResource.java
@@ -33,12 +33,13 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
-@Profile("test")
+@Profile(FineractProfiles.TEST)
@Component
@Path("/v1/instance-mode")
@Tag(name = "Instance Mode", description = "Instance mode changing API")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/s3/LocalstackS3ClientCustomizer.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/s3/LocalstackS3ClientCustomizer.java
index 6b764aa6a..4e7d10853 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/s3/LocalstackS3ClientCustomizer.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/s3/LocalstackS3ClientCustomizer.java
@@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.s3;
import java.net.URI;
import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import org.apache.poi.util.StringUtil;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
@@ -28,7 +29,7 @@ import software.amazon.awssdk.services.s3.S3ClientBuilder;
@Component
@RequiredArgsConstructor
-@Profile("test")
+@Profile(FineractProfiles.TEST)
public class LocalstackS3ClientCustomizer implements S3ClientCustomizer {
private final Environment environment;
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java
index 6b1342b53..ae454ca08 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java
@@ -37,6 +37,7 @@ import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.portfolio.client.domain.Client;
@@ -45,7 +46,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
-@Profile("test")
+@Profile(FineractProfiles.TEST)
@Component
@Path("/v1/internal/client")
@RequiredArgsConstructor
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
index 7969fd98f..a15d7f4a1 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
@@ -38,6 +38,7 @@ import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -50,7 +51,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
-@Profile("test")
+@Profile(FineractProfiles.TEST)
@Component
@Path("/v1/internal/loan")
@RequiredArgsConstructor
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckDueInstallmentsBusinessStepTest.java
b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckDueInstallmentsBusinessStepTest.java
new file mode 100644
index 000000000..161123c8c
--- /dev/null
+++
b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckDueInstallmentsBusinessStepTest.java
@@ -0,0 +1,161 @@
+/**
+ * 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.cob.loan;
+
+import static org.mockito.Mockito.times;
+
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.domain.ActionContext;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
+import
org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountCustomSnapshotBusinessEvent;
+import
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+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.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class CheckDueInstallmentsBusinessStepTest {
+
+ @Mock
+ private BusinessEventNotifierService businessEventNotifierService;
+
+ @Captor
+ private ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor;
+
+ @InjectMocks
+ private CheckDueInstallmentsBusinessStep underTest;
+
+ /**
+ * Setup context before each test.
+ */
+ @BeforeEach
+ public void setUp() {
+ ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L,
"default", "Default", "Asia/Kolkata", null));
+ ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
+ ThreadLocalContextUtil.setBusinessDates(new
HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.parse("2024-01-16"),
+ BusinessDateType.COB_DATE, LocalDate.parse("2024-01-15"))));
+ }
+
+ @AfterEach
+ public void tearDown() {
+ ThreadLocalContextUtil.reset();
+ }
+
+ @Test
+ public void testNoRepaymentScheduleInLoan() {
+ // given
+ Loan loan = Mockito.mock(Loan.class);
+
+ // when
+ underTest.execute(loan);
+
+ // then
+ Mockito.verifyNoInteractions(businessEventNotifierService);
+ }
+
+ @Test
+ public void testInstallmentDueDateIsNotMatchingWithCurrentBusinessDate() {
+ // given
+ Loan loan = Mockito.mock(Loan.class);
+
Mockito.when(loan.getRepaymentScheduleInstallments()).thenReturn(List.of(Mockito.mock(LoanRepaymentScheduleInstallment.class)));
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).getDueDate()).thenReturn(LocalDate.parse("2024-01-17"));
+
+ // when
+ underTest.execute(loan);
+
+ // then
+ Mockito.verifyNoInteractions(businessEventNotifierService);
+ }
+
+ @Test
+ public void
testSingleInstallmentDueDateIsMatchingWithCurrentBusinessDateAndNotFullyPayed()
{
+ // given
+ Loan loan = Mockito.mock(Loan.class);
+
Mockito.when(loan.getRepaymentScheduleInstallments()).thenReturn(List.of(Mockito.mock(LoanRepaymentScheduleInstallment.class)));
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).getDueDate()).thenReturn(LocalDate.parse("2024-01-16"));
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).isNotFullyPaidOff()).thenReturn(true);
+
+ // when
+ underTest.execute(loan);
+
+ // then
+ Mockito.verify(businessEventNotifierService,
times(1)).notifyPostBusinessEvent(businessEventArgumentCaptor.capture());
+ BusinessEvent<?> rawEvent = businessEventArgumentCaptor.getValue();
+
Assertions.assertInstanceOf(LoanAccountCustomSnapshotBusinessEvent.class,
rawEvent);
+ LoanAccountCustomSnapshotBusinessEvent event =
(LoanAccountCustomSnapshotBusinessEvent) rawEvent;
+ Assertions.assertEquals(loan, event.get());
+ }
+
+ @Test
+ public void
testSingleInstallmentDueDateIsMatchingWithCurrentBusinessDateAndFullyPayed() {
+ // given
+ Loan loan = Mockito.mock(Loan.class);
+
Mockito.when(loan.getRepaymentScheduleInstallments()).thenReturn(List.of(Mockito.mock(LoanRepaymentScheduleInstallment.class)));
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).getDueDate()).thenReturn(LocalDate.parse("2024-01-16"));
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).isNotFullyPaidOff()).thenReturn(false);
+
+ // when
+ underTest.execute(loan);
+
+ // then
+ Mockito.verifyNoInteractions(businessEventNotifierService);
+ }
+
+ @Test
+ public void
testMultipleInstallmentDueDateIsMatchingWithCurrentBusinessDateAndNotFullyPayedButSingleEventIsGenerated()
{
+ // given
+ Loan loan = Mockito.mock(Loan.class);
+ Mockito.when(loan.getRepaymentScheduleInstallments()).thenReturn(
+ List.of(Mockito.mock(LoanRepaymentScheduleInstallment.class),
Mockito.mock(LoanRepaymentScheduleInstallment.class)));
+ // first one is a down payment installment
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).getDueDate()).thenReturn(LocalDate.parse("2024-01-16"));
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(0).isNotFullyPaidOff()).thenReturn(true);
+
Mockito.lenient().when(loan.getRepaymentScheduleInstallments().get(0).isDownPayment()).thenReturn(true);
+ // this one is a real installment
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(1).getDueDate()).thenReturn(LocalDate.parse("2024-01-16"));
+
Mockito.when(loan.getRepaymentScheduleInstallments().get(1).isNotFullyPaidOff()).thenReturn(true);
+
+ // when
+ underTest.execute(loan);
+
+ // then
+ Mockito.verify(businessEventNotifierService,
times(1)).notifyPostBusinessEvent(businessEventArgumentCaptor.capture());
+ BusinessEvent<?> rawEvent = businessEventArgumentCaptor.getValue();
+
Assertions.assertInstanceOf(LoanAccountCustomSnapshotBusinessEvent.class,
rawEvent);
+ LoanAccountCustomSnapshotBusinessEvent event =
(LoanAccountCustomSnapshotBusinessEvent) rawEvent;
+ Assertions.assertEquals(loan, event.get());
+ }
+
+}
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
index ca46595cf..69b69d9db 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
@@ -98,7 +98,7 @@ public class ExternalEventConfigurationValidationServiceTest {
"LoanChargeOffPostBusinessEvent",
"LoanUndoChargeOffBusinessEvent", "LoanAccrualTransactionCreatedBusinessEvent",
"LoanRescheduledDueAdjustScheduleBusinessEvent",
"LoanOwnershipTransferBusinessEvent", "LoanAccountSnapshotBusinessEvent",
"LoanTransactionDownPaymentPostBusinessEvent",
"LoanTransactionDownPaymentPreBusinessEvent",
- "LoanAccountDelinquencyPauseChangedBusinessEvent");
+ "LoanAccountDelinquencyPauseChangedBusinessEvent",
"LoanAccountCustomSnapshotBusinessEvent");
List<FineractPlatformTenant> tenants = Arrays
.asList(new FineractPlatformTenant(1L, "default", "Default
Tenant", "Europe/Budapest", null));
@@ -178,7 +178,7 @@ public class
ExternalEventConfigurationValidationServiceTest {
"LoanUndoChargeOffBusinessEvent",
"LoanAccrualTransactionCreatedBusinessEvent",
"LoanRescheduledDueAdjustScheduleBusinessEvent",
"LoanOwnershipTransferBusinessEvent", "LoanAccountSnapshotBusinessEvent",
"LoanTransactionDownPaymentPostBusinessEvent",
"LoanTransactionDownPaymentPreBusinessEvent",
- "LoanAccountDelinquencyPauseChangedBusinessEvent");
+ "LoanAccountDelinquencyPauseChangedBusinessEvent",
"LoanAccountCustomSnapshotBusinessEvent");
List<FineractPlatformTenant> tenants = Arrays
.asList(new FineractPlatformTenant(1L, "default", "Default
Tenant", "Europe/Budapest", null));
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index e04e414b0..dcc4c1d8a 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -74,6 +74,8 @@ import
org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -86,7 +88,7 @@ public abstract class BaseLoanIntegrationTest {
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
- protected final ResponseSpecification responseSpec =
createResponseSpecification(200);
+ protected final ResponseSpecification responseSpec =
createResponseSpecification(Matchers.is(200));
protected final RequestSpecification requestSpec =
createRequestSpecification();
protected final AccountHelper accountHelper = new
AccountHelper(requestSpec, responseSpec);
@@ -261,8 +263,8 @@ public abstract class BaseLoanIntegrationTest {
return request;
}
- private static ResponseSpecification createResponseSpecification(int
statusCode) {
- return new ResponseSpecBuilder().expectStatusCode(statusCode).build();
+ protected static ResponseSpecification
createResponseSpecification(Matcher<Integer> statusCodeMatcher) {
+ return new
ResponseSpecBuilder().expectStatusCode(statusCodeMatcher).build();
}
protected void verifyUndoLastDisbursalShallFail(Long loanId, String
expectedError) {
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CustomSnapshotEventIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CustomSnapshotEventIntegrationTest.java
new file mode 100644
index 000000000..d2a29aa94
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CustomSnapshotEventIntegrationTest.java
@@ -0,0 +1,326 @@
+/**
+ * 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.integrationtests;
+
+import static
org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
+
+import com.google.gson.Gson;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.BusinessDateRequest;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+import
org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
+import org.apache.fineract.integrationtests.common.BusinessStepHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import
org.apache.fineract.integrationtests.common.ExternalEventConfigurationHelper;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import
org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper;
+import
org.apache.fineract.integrationtests.common.externalevents.ExternalEventsExtension;
+import
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@Slf4j
+@ExtendWith({ LoanTestLifecycleExtension.class, ExternalEventsExtension.class
})
+public class CustomSnapshotEventIntegrationTest extends
BaseLoanIntegrationTest {
+
+ private SchedulerJobHelper schedulerJobHelper = new
SchedulerJobHelper(this.requestSpec);
+
+ @Test
+ public void testSnapshotEventGenerationWhenLoanInstallmentIsNotPayed() {
+ runAt("31 January 2023", () -> {
+ // Enable Business Step
+ enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS",
"LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
+ "CHECK_LOAN_REPAYMENT_OVERDUE",
"UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
+ "EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS");
+
+ enableLoanAccountCustomSnapshotBusinessEvent();
+
+ // Create Client
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ // Create Loan Product
+ PostLoanProductsRequest loanProductsRequest =
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+ InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+ loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+ // Apply and Approve Loan
+ Long loanId = applyAndApproveLoan(clientId,
loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
+
+ // Disburse Loan
+ disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(0, null, "01 January 2023"), //
+ installment(313.0, false, "31 January 2023"), //
+ installment(313.0, false, "02 March 2023"), //
+ installment(313.0, false, "01 April 2023"), //
+ installment(311.0, false, "01 May 2023") //
+ );
+
+ // delete all external events
+ deleteAllExternalEvents();
+
+ // run cob
+ updateBusinessDateAndExecuteCOBJob("01 February 2023");
+
+ // verify external events
+ List<ExternalEventDTO> allExternalEvents =
ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
+ Assertions.assertEquals(1, allExternalEvents.size());
+ Assertions.assertEquals("LoanAccountCustomSnapshotBusinessEvent",
allExternalEvents.get(0).getType());
+ Assertions.assertEquals(loanId,
allExternalEvents.get(0).getAggregateRootId());
+ });
+ }
+
+ @Test
+ public void testNoSnapshotEventGenerationWhenLoanInstallmentIsPayed() {
+ runAt("31 January 2023", () -> {
+ // Enable Business Step
+ enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS",
"LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
+ "CHECK_LOAN_REPAYMENT_OVERDUE",
"UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
+ "EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS");
+
+ enableLoanAccountCustomSnapshotBusinessEvent();
+
+ // Create Client
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ // Create Loan Product
+ PostLoanProductsRequest loanProductsRequest =
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+ InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+ loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+ // Apply and Approve Loan
+ Long loanId = applyAndApproveLoan(clientId,
loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
+
+ // Disburse Loan
+ disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(0, null, "01 January 2023"), //
+ installment(313.0, false, "31 January 2023"), //
+ installment(313.0, false, "02 March 2023"), //
+ installment(313.0, false, "01 April 2023"), //
+ installment(311.0, false, "01 May 2023") //
+ );
+
+ addRepaymentForLoan(loanId, 313.0, "31 January 2023");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(0, null, "01 January 2023"), //
+ installment(313.0, true, "31 January 2023"), //
+ installment(313.0, false, "02 March 2023"), //
+ installment(313.0, false, "01 April 2023"), //
+ installment(311.0, false, "01 May 2023") //
+ );
+
+ // delete all external events
+ deleteAllExternalEvents();
+
+ // run cob
+ updateBusinessDateAndExecuteCOBJob("01 February 2023");
+
+ // verify external events
+ List<ExternalEventDTO> allExternalEvents =
ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
+ Assertions.assertEquals(0, allExternalEvents.size());
+ });
+ }
+
+ @Test
+ public void
testNoSnapshotEventGenerationWhenWhenCustomSnapshotEventCOBTaskIsNotActive() {
+ runAt("31 January 2023", () -> {
+ // Enable Business Step
+ enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS",
"LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
+ "CHECK_LOAN_REPAYMENT_OVERDUE",
"UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
+ "EXTERNAL_ASSET_OWNER_TRANSFER");
+
+ enableLoanAccountCustomSnapshotBusinessEvent();
+
+ // Create Client
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ // Create Loan Product
+ PostLoanProductsRequest loanProductsRequest =
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+ InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+ loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+ // Apply and Approve Loan
+ Long loanId = applyAndApproveLoan(clientId,
loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
+
+ // Disburse Loan
+ disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(0, null, "01 January 2023"), //
+ installment(313.0, false, "31 January 2023"), //
+ installment(313.0, false, "02 March 2023"), //
+ installment(313.0, false, "01 April 2023"), //
+ installment(311.0, false, "01 May 2023") //
+ );
+
+ // delete all external events
+ deleteAllExternalEvents();
+
+ // run cob
+ updateBusinessDateAndExecuteCOBJob("01 February 2023");
+
+ // verify external events
+ List<ExternalEventDTO> allExternalEvents =
ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
+ Assertions.assertEquals(0, allExternalEvents.size());
+ });
+ }
+
+ @Test
+ public void
testNoSnapshotEventGenerationWhenCOBDateIsNotMatchingWithInstallmentDueDate() {
+ runAt("30 January 2023", () -> {
+ // Enable Business Step
+ enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS",
"LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
+ "CHECK_LOAN_REPAYMENT_OVERDUE",
"UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
+ "EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS");
+
+ enableLoanAccountCustomSnapshotBusinessEvent();
+
+ // Create Client
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ // Create Loan Product
+ PostLoanProductsRequest loanProductsRequest =
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+ InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+ loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+ // Apply and Approve Loan
+ Long loanId = applyAndApproveLoan(clientId,
loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
+
+ // Disburse Loan
+ disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(0, null, "01 January 2023"), //
+ installment(313.0, false, "31 January 2023"), //
+ installment(313.0, false, "02 March 2023"), //
+ installment(313.0, false, "01 April 2023"), //
+ installment(311.0, false, "01 May 2023") //
+ );
+
+ // delete all external events
+ deleteAllExternalEvents();
+
+ // run cob
+ updateBusinessDateAndExecuteCOBJob("31 January 2023");
+
+ // verify external events
+ List<ExternalEventDTO> allExternalEvents =
ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
+ Assertions.assertEquals(0, allExternalEvents.size());
+ });
+ }
+
+ @Test
+ public void
testNoSnapshotEventGenerationWhenCustomSnapshotEventIsDisabled() {
+ runAt("31 January 2023", () -> {
+ // Enable Business Step
+ enableCOBBusinessStep("APPLY_CHARGE_TO_OVERDUE_LOANS",
"LOAN_DELINQUENCY_CLASSIFICATION", "CHECK_LOAN_REPAYMENT_DUE",
+ "CHECK_LOAN_REPAYMENT_OVERDUE",
"UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
+ "EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS");
+
+ // Create Client
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ // Create Loan Product
+ PostLoanProductsRequest loanProductsRequest =
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+ InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+ loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+ // Apply and Approve Loan
+ Long loanId = applyAndApproveLoan(clientId,
loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
+
+ // Disburse Loan
+ disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(0, null, "01 January 2023"), //
+ installment(313.0, false, "31 January 2023"), //
+ installment(313.0, false, "02 March 2023"), //
+ installment(313.0, false, "01 April 2023"), //
+ installment(311.0, false, "01 May 2023") //
+ );
+
+ // delete all external events
+ deleteAllExternalEvents();
+
+ // run cob
+ updateBusinessDateAndExecuteCOBJob("01 February 2023");
+
+ // verify external events
+ List<ExternalEventDTO> allExternalEvents =
ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
+ Assertions.assertEquals(0, allExternalEvents.size());
+ });
+ }
+
+ private void deleteAllExternalEvents() {
+ ExternalEventHelper.deleteAllExternalEvents(requestSpec,
createResponseSpecification(Matchers.is(204)));
+ List<ExternalEventDTO> allExternalEvents =
ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
+ Assertions.assertEquals(0, allExternalEvents.size());
+ }
+
+ private void enableCOBBusinessStep(String... steps) {
+ new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", steps);
+
+ }
+
+ public static String getExternalEventConfigurationsForUpdateJSON() {
+ Map<String, Map<String, Boolean>> configurationsForUpdate = new
HashMap<>();
+ Map<String, Boolean> configurations = new HashMap<>();
+ configurations.put("CentersCreateBusinessEvent", true);
+ configurations.put("ClientActivateBusinessEvent", true);
+ configurationsForUpdate.put("externalEventConfigurations",
configurations);
+ return new Gson().toJson(configurationsForUpdate);
+ }
+
+ private void enableLoanAccountCustomSnapshotBusinessEvent() {
+ final Map<String, Boolean> updatedConfigurations =
ExternalEventConfigurationHelper.updateExternalEventConfigurations(requestSpec,
+ responseSpec,
"{\"externalEventConfigurations\":{\"LoanAccountCustomSnapshotBusinessEvent\":true}}\n");
+ Assertions.assertEquals(updatedConfigurations.size(), 1);
+
Assertions.assertTrue(updatedConfigurations.containsKey("LoanAccountCustomSnapshotBusinessEvent"));
+
Assertions.assertTrue(updatedConfigurations.get("LoanAccountCustomSnapshotBusinessEvent"));
+ }
+
+ private void updateBusinessDateAndExecuteCOBJob(String date) {
+ businessDateHelper.updateBusinessDate(
+ new
BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en"));
+ schedulerJobHelper.executeAndAwaitJob("Loan COB");
+ }
+
+}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
index 29bdab718..42c5e1dd3 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
@@ -495,6 +495,11 @@ public class ExternalEventConfigurationHelper {
loanAccountDelinquencyPauseChangedBusinessEvent.put("enabled", false);
defaults.add(loanAccountDelinquencyPauseChangedBusinessEvent);
+ Map<String, Object> loanAccountCustomSnapshotBusinessEvent = new
HashMap<>();
+ loanAccountCustomSnapshotBusinessEvent.put("type",
"LoanAccountCustomSnapshotBusinessEvent");
+ loanAccountCustomSnapshotBusinessEvent.put("enabled", false);
+ defaults.add(loanAccountCustomSnapshotBusinessEvent);
+
return defaults;
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
index 5fc131b0e..261f7b976 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
@@ -283,6 +283,9 @@ public final class Utils {
final String deleteURL, final String jsonAttributeToGetBack) {
final String json =
given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().delete(deleteURL).andReturn()
.asString();
+ if (jsonAttributeToGetBack == null) {
+ return (T) json;
+ }
return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/ExternalEventHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/ExternalEventHelper.java
new file mode 100644
index 000000000..793e14444
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/ExternalEventHelper.java
@@ -0,0 +1,92 @@
+/**
+ * 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.integrationtests.common.externalevents;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.List;
+import lombok.Builder;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.util.JSON;
+import
org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
+import org.apache.fineract.integrationtests.common.Utils;
+
+@Slf4j
+public final class ExternalEventHelper {
+
+ private static final Gson GSON = new JSON().getGson();
+
+ private ExternalEventHelper() {}
+
+ @Builder
+ public static class Filter {
+
+ private final String idempotencyKey;
+ private final String type;
+ private final String category;
+ private final Long aggregateRootId;
+
+ public String toQueryParams() {
+ StringBuilder stringBuilder = new StringBuilder();
+ if (idempotencyKey != null) {
+
stringBuilder.append("idempotencyKey=").append(idempotencyKey).append("&");
+ }
+
+ if (type != null) {
+ stringBuilder.append("type=").append(type).append("&");
+ }
+
+ if (category != null) {
+ stringBuilder.append("category=").append(category).append("&");
+ }
+
+ if (aggregateRootId != null) {
+
stringBuilder.append("aggregateRootId=").append(aggregateRootId).append("&");
+ }
+
+ return stringBuilder.toString();
+
+ }
+ }
+
+ public static List<ExternalEventDTO> getAllExternalEvents(final
RequestSpecification requestSpec,
+ final ResponseSpecification responseSpec) {
+ final String url =
"/fineract-provider/api/v1/internal/externalevents?" + Utils.TENANT_IDENTIFIER;
+ log.info("---------------------------------GETTING ALL EXTERNAL
EVENTS---------------------------------------------");
+ String response = Utils.performServerGet(requestSpec, responseSpec,
url);
+ return GSON.fromJson(response, new TypeToken<List<ExternalEventDTO>>()
{}.getType());
+ }
+
+ public static List<ExternalEventDTO> getAllExternalEvents(final
RequestSpecification requestSpec,
+ final ResponseSpecification responseSpec, Filter filter) {
+ final String url =
"/fineract-provider/api/v1/internal/externalevents?" + filter.toQueryParams() +
Utils.TENANT_IDENTIFIER;
+ log.info("---------------------------------GETTING ALL EXTERNAL
EVENTS---------------------------------------------");
+ String response = Utils.performServerGet(requestSpec, responseSpec,
url);
+ return GSON.fromJson(response, new TypeToken<List<ExternalEventDTO>>()
{}.getType());
+ }
+
+ public static void deleteAllExternalEvents(final RequestSpecification
requestSpec, final ResponseSpecification responseSpec) {
+ final String url =
"/fineract-provider/api/v1/internal/externalevents?" + Utils.TENANT_IDENTIFIER;
+ log.info("-----------------------------DELETE ALL EXTERNAL EVENTS
PARTITIONS----------------------------------------");
+ Utils.performServerDelete(requestSpec, responseSpec, url, null);
+ }
+
+}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/ExternalEventsExtension.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/ExternalEventsExtension.java
new file mode 100644
index 000000000..80befb045
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/ExternalEventsExtension.java
@@ -0,0 +1,88 @@
+/**
+ * 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.integrationtests.common.externalevents;
+
+import static
org.apache.fineract.integrationtests.common.Utils.initializeRESTAssured;
+
+import com.google.common.collect.MapDifference;
+import com.google.common.collect.Maps;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import
org.apache.fineract.integrationtests.common.ExternalEventConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+@Slf4j
+public class ExternalEventsExtension implements AfterEachCallback,
BeforeEachCallback {
+
+ private Map<String, Boolean> original;
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+
+ public ExternalEventsExtension() {
+ initializeRESTAssured();
+ this.requestSpec = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ this.requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ this.responseSpec = new
ResponseSpecBuilder().expectStatusCode(200).build();
+ this.requestSpec.header("Fineract-Platform-TenantId", "default");
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) {
+ ArrayList<Map<String, Object>> allExternalEventConfigurations =
ExternalEventConfigurationHelper
+ .getAllExternalEventConfigurations(requestSpec, responseSpec);
+ Map<String, Boolean> collected =
allExternalEventConfigurations.stream()
+ .map(map -> Map.entry((String) map.get("type"), (Boolean)
map.get("enabled")))
+ .collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue));
+
+ Map<String, MapDifference.ValueDifference<Boolean>> diff =
Maps.difference(original, collected).entriesDiffering();
+ diff.keySet().forEach(key -> {
+ MapDifference.ValueDifference<Boolean> valueDifference =
diff.get(key);
+ log.debug("External event {} changed from {} to {}. Restoring to
its original state.", key, valueDifference.leftValue(),
+ valueDifference.rightValue());
+ restore(key, valueDifference.leftValue());
+ });
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext context) {
+ ArrayList<Map<String, Object>> allExternalEventConfigurations =
ExternalEventConfigurationHelper
+ .getAllExternalEventConfigurations(requestSpec, responseSpec);
+ original = allExternalEventConfigurations.stream().map(map ->
Map.entry((String) map.get("type"), (Boolean) map.get("enabled")))
+ .collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue));
+ }
+
+ private void restore(String key, Boolean value) {
+ final Map<String, Boolean> updatedConfigurations =
ExternalEventConfigurationHelper.updateExternalEventConfigurations(requestSpec,
+ responseSpec, "{\"externalEventConfigurations\":{\"" + key +
"\":" + value + "}}\n");
+ Assertions.assertEquals(updatedConfigurations.size(), 1);
+ Assertions.assertTrue(updatedConfigurations.containsKey(key));
+ Assertions.assertEquals(value, updatedConfigurations.get(key));
+ }
+}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java
index 6b4188a36..9e7bb38d6 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java
@@ -23,16 +23,17 @@ import io.restassured.specification.ResponseSpecification;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
-import org.apache.fineract.integrationtests.client.IntegrationTest;
import org.apache.fineract.integrationtests.common.Utils;
@Slf4j
-public class CobHelper extends IntegrationTest {
+public final class CobHelper {
+
+ private CobHelper() {}
public static List<Map<String, Object>> getCobPartitions(final
RequestSpecification requestSpec,
final ResponseSpecification responseSpec, int partitionSize, final
String jsonReturn) {
- final String GET_LOAN_URL =
"/fineract-provider/api/v1/internal/cob/partitions/" + partitionSize + "?" +
Utils.TENANT_IDENTIFIER;
+ final String url =
"/fineract-provider/api/v1/internal/cob/partitions/" + partitionSize + "?" +
Utils.TENANT_IDENTIFIER;
log.info("---------------------------------GET COB
PARTITIONS---------------------------------------------");
- return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_URL,
jsonReturn);
+ return Utils.performServerGet(requestSpec, responseSpec, url,
jsonReturn);
}
}