package az.kapitalbank.atlas.incoming.transfer.handler.service;

import static az.kapitalbank.atlas.incoming.transfer.handler.config.properties.ApplicationConstants.MX;
import static java.time.LocalTime.now;

import az.kapitalbank.atlas.incoming.transfer.handler.domain.IncomingTransferProcessLog;
import az.kapitalbank.atlas.incoming.transfer.handler.domain.MT103IncomingTransfer;
import az.kapitalbank.atlas.incoming.transfer.handler.domain.ProcessStep;
import az.kapitalbank.atlas.incoming.transfer.handler.util.SwiftMessageUtil;
import az.kapitalbank.atlas.incoming.transfer.handler.util.XmlUtil;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class TransferRetryScheduler {

private final MT103Service mt103Service;
private final StepLoggerService stepLoggerService;
private final TransferProcessorService transferProcessorService;

@Scheduled(cron = "${mt103.retry.failedSteps}")
public void reprocessFailedSteps() {
log.debug("Starting scheduled reprocessing of failed steps at {}", now());

List<IncomingTransferProcessLog> incomingTransferProcessLogs = stepLoggerService.findAllUnprocessedEntities();
if (incomingTransferProcessLogs.isEmpty()) {
log.info("No failed logs found for retryable steps.");
return;
}

for (IncomingTransferProcessLog incomingTransferProcessLog : incomingTransferProcessLogs) {
String gpiRef = incomingTransferProcessLog.getGpiRef();
ProcessStep processStep = incomingTransferProcessLog.getStep();

log.debug("Reprocessing GpiRef={} at failed step {}", gpiRef, processStep);

mt103Service.findById(gpiRef).ifPresentOrElse(
entity -> reprocessEntity(entity, processStep),
() -> log.warn("No MT103Entity found for GpiRef {}. Skipping...", gpiRef));
}

log.info("Finished reprocessing of failed steps at {}", now());
}

private void reprocessEntity(MT103IncomingTransfer entity, ProcessStep processStep) {
String gpiRef = entity.getId();
String content = entity.getPayload();
String body = MX.equals(entity.getType())
? XmlUtil.extractDocumentBlock(content)
: SwiftMessageUtil.extractBlock4(SwiftMessageUtil.parseSwiftMessage(content));

String sendingBic = null;
String receivingBic = null;

if (MX.equals(entity.getType())) {
sendingBic = XmlUtil.extractSendingBic(content);
receivingBic = XmlUtil.extractReceivingBic(content);
}

transferProcessorService.resumeFlow(content, gpiRef, body, processStep,
entity.getType(), sendingBic, receivingBic);
}

}



package az.kapital.atlas.incoming.transfer.handler.service;

import static az.kapital.atlas.incoming.transfer.handler.properties.TestConstants.BLOCK4;
import static az.kapital.atlas.incoming.transfer.handler.properties.TestConstants.MT103CONTENT;
import static az.kapitalbank.atlas.incoming.transfer.handler.config.properties.ApplicationConstants.MT_103;
import static az.kapitalbank.atlas.incoming.transfer.handler.config.properties.ApplicationConstants.MX;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import az.kapitalbank.atlas.incoming.transfer.handler.domain.IncomingTransferProcessLog;
import az.kapitalbank.atlas.incoming.transfer.handler.domain.MT103IncomingTransfer;
import az.kapitalbank.atlas.incoming.transfer.handler.domain.ProcessStep;
import az.kapitalbank.atlas.incoming.transfer.handler.service.MT103Service;
import az.kapitalbank.atlas.incoming.transfer.handler.service.StepLoggerService;
import az.kapitalbank.atlas.incoming.transfer.handler.service.TransferProcessorService;
import az.kapitalbank.atlas.incoming.transfer.handler.service.TransferRetryScheduler;
import az.kapitalbank.atlas.incoming.transfer.handler.util.SwiftMessageUtil;
import az.kapitalbank.atlas.incoming.transfer.handler.util.XmlUtil;
import com.prowidesoftware.swift.model.SwiftMessage;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class TransferRetrySchedulerTest {

@Mock
StepLoggerService stepLoggerService;
@Mock
MT103Service mt103Service;
@Mock
TransferProcessorService transferProcessorService;

@InjectMocks
TransferRetryScheduler scheduler;

private static IncomingTransferProcessLog mockLog(String ref, ProcessStep step) {
IncomingTransferProcessLog log = mock(IncomingTransferProcessLog.class);
when(log.getGpiRef()).thenReturn(ref);
when(log.getStep()).thenReturn(step);
return log;
}

private static MT103IncomingTransfer entity(String id, String payload, String type) {
MT103IncomingTransfer e = new MT103IncomingTransfer();
e.setId(id);
e.setPayload(payload);
e.setType(type);
return e;
}

@Test
void reprocessFailedSteps_noLogs_quickExit() {
when(stepLoggerService.findAllUnprocessedEntities()).thenReturn(Collections.emptyList());
scheduler.reprocessFailedSteps();
verifyNoInteractions(mt103Service, transferProcessorService);
}

@Test
void reprocessFailedSteps_MT_calls_resumeFlow_with_block4_type_and_null_bics() {
String gpi = "OK";
var log = mockLog(gpi, ProcessStep.VERIFY_TRANSFER);
when(stepLoggerService.findAllUnprocessedEntities()).thenReturn(List.of(log));

var e = entity(gpi, MT103CONTENT, MT_103);
when(mt103Service.findById(gpi)).thenReturn(Optional.of(e));

try (MockedStatic<SwiftMessageUtil> util = mockStatic(SwiftMessageUtil.class)) {

SwiftMessage sm = mock(SwiftMessage.class);
util.when(() -> SwiftMessageUtil.parseSwiftMessage(e.getPayload())).thenReturn(sm);
util.when(() -> SwiftMessageUtil.extractBlock4(sm)).thenReturn(BLOCK4);

scheduler.reprocessFailedSteps();

verify(transferProcessorService).resumeFlow(
eq(e.getPayload()),
eq(gpi),
eq(BLOCK4),
eq(ProcessStep.VERIFY_TRANSFER),
eq(MT_103),
eq(null),
eq(null)
);
}
}

@Test
void reprocessFailedSteps_MX_calls_resumeFlow_with_document_type_and_bics() {
String uetr = "MX-REF";
var log = mockLog(uetr, ProcessStep.VERIFY_TRANSFER);
when(stepLoggerService.findAllUnprocessedEntities()).thenReturn(List.of(log));

String mxEnvelope = """
<Envelope>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:">
<FIToFICstmrCdtTrf><GrpHdr><MsgId>X</MsgId></GrpHdr></FIToFICstmrCdtTrf>
</Document>
</Envelope>
""";
var e = entity(uetr, mxEnvelope, MX);
when(mt103Service.findById(uetr)).thenReturn(Optional.of(e));

try (MockedStatic<XmlUtil> xmlMock = mockStatic(XmlUtil.class)) {
xmlMock.when(() -> XmlUtil.extractDocumentBlock(mxEnvelope))
.thenReturn("<Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:\"><FIToFICstmrCdtTrf/></Document>");
xmlMock.when(() -> XmlUtil.extractSendingBic(mxEnvelope)).thenReturn("AIIBAZ20XXX");
xmlMock.when(() -> XmlUtil.extractReceivingBic(mxEnvelope)).thenReturn("IRVTUS3NXXX");

scheduler.reprocessFailedSteps();

verify(transferProcessorService).resumeFlow(
eq(e.getPayload()),
eq(uetr),
eq("<Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:\"><FIToFICstmrCdtTrf/></Document>"),
eq(ProcessStep.VERIFY_TRANSFER),
eq(MX),
eq("AIIBAZ20XXX"),
eq("IRVTUS3NXXX")
);
}
}

@Test
void reprocessFailedSteps_entityMissing_skips() {
String gpi = "MISS";
var log = mockLog(gpi, ProcessStep.VERIFY_TRANSFER);
when(stepLoggerService.findAllUnprocessedEntities()).thenReturn(List.of(log));
when(mt103Service.findById(gpi)).thenReturn(Optional.empty());

scheduler.reprocessFailedSteps();

verify(mt103Service).findById(gpi);
verifyNoInteractions(transferProcessorService);
}

}


























 

Комментарии

Популярные сообщения из этого блога

IoC:ApplicationContext, BeanFactory. Bean

Lesson1: JDK, JVM, JRE

Lesson_2: Operations in Java