feat(training): integrate issuance and exchange workflows
parent
c92d26bdce
commit
3e76132ad9
@ -0,0 +1,22 @@
|
||||
package com.yau.digitalrmb.issuance.application.query;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class DigitalCurrencyProductionDigestView {
|
||||
private final DigitalCurrencyProductionBatchView batch;
|
||||
private final List<CoinDigestView> digests;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class CoinDigestView {
|
||||
private final String coinId;
|
||||
private final BigDecimal denomination;
|
||||
private final String digest;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package com.yau.digitalrmb.issuance.application.service;
|
||||
|
||||
import com.yau.digitalrmb.issuance.application.query.ReserveDeductionExecutionView;
|
||||
import com.yau.digitalrmb.issuance.domain.model.CentralBankInstitutionAccount;
|
||||
import com.yau.digitalrmb.issuance.domain.model.DigitalCurrencyProductionBatch;
|
||||
import com.yau.digitalrmb.issuance.domain.model.IssuanceAuditActor;
|
||||
import com.yau.digitalrmb.issuance.domain.model.ReserveDeductionExecution;
|
||||
import com.yau.digitalrmb.issuance.domain.model.ReserveDeductionNotification;
|
||||
import com.yau.digitalrmb.issuance.domain.repository.CentralBankInstitutionAccountRepository;
|
||||
import com.yau.digitalrmb.issuance.domain.repository.DigitalCurrencyProductionRepository;
|
||||
import com.yau.digitalrmb.issuance.domain.repository.ReserveDeductionExecutionRepository;
|
||||
import com.yau.digitalrmb.issuance.domain.repository.ReserveDeductionNotificationRepository;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class ReserveDeductionExecutionServiceTest {
|
||||
private final ReserveDeductionNotificationRepository notifications = mock(ReserveDeductionNotificationRepository.class);
|
||||
private final ReserveDeductionExecutionRepository executions = mock(ReserveDeductionExecutionRepository.class);
|
||||
private final CentralBankInstitutionAccountRepository accounts = mock(CentralBankInstitutionAccountRepository.class);
|
||||
private final DigitalCurrencyProductionRepository productions = mock(DigitalCurrencyProductionRepository.class);
|
||||
private final ReserveDeductionExecutionService service =
|
||||
new ReserveDeductionExecutionService(notifications, executions, accounts, productions);
|
||||
|
||||
@Test
|
||||
void restoresCurrentBalanceAndPreservesOriginalDeductionAudit() {
|
||||
UUID requestId = UUID.fromString("00000000-0000-0000-0000-000000000501");
|
||||
ReserveDeductionExecution original = execution(requestId);
|
||||
ReserveDeductionNotification notification = notification(requestId);
|
||||
when(executions.findByTransactionIdForUpdate("RESERVE-501")).thenReturn(Optional.of(original));
|
||||
when(productions.findByRequestId(requestId)).thenReturn(Optional.empty());
|
||||
when(notifications.findByTransactionId("RESERVE-501")).thenReturn(Optional.of(notification));
|
||||
when(accounts.findByBankCodeForUpdate("BANK-501")).thenReturn(Optional.of(account("125000.00")));
|
||||
|
||||
ReserveDeductionExecutionView result = service.reverse("RESERVE-501", new IssuanceAuditActor("user-2", "reviewer"));
|
||||
|
||||
assertThat(result.getStatus()).isEqualTo("REVERSED");
|
||||
assertThat(result.getAfterBalance()).isEqualByComparingTo("150000.00");
|
||||
assertThat(result.getReversedBalance()).isEqualByComparingTo("175000.00");
|
||||
assertThat(result.getExecutedBy()).isEqualTo("operator");
|
||||
assertThat(result.getReversedBy()).isEqualTo("reviewer");
|
||||
verify(accounts).updateReserveBalance("BANK-501", new BigDecimal("175000.00"));
|
||||
ArgumentCaptor<ReserveDeductionExecution> saved = ArgumentCaptor.forClass(ReserveDeductionExecution.class);
|
||||
verify(executions).save(saved.capture());
|
||||
assertThat(saved.getValue().getBeforeBalance()).isEqualByComparingTo("200000.00");
|
||||
assertThat(saved.getValue().getAfterBalance()).isEqualByComparingTo("150000.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsReversalWhenProductionBatchExists() {
|
||||
UUID requestId = UUID.fromString("00000000-0000-0000-0000-000000000502");
|
||||
when(executions.findByTransactionIdForUpdate("RESERVE-501")).thenReturn(Optional.of(execution(requestId)));
|
||||
DigitalCurrencyProductionBatch batch = new DigitalCurrencyProductionBatch("MINT-502", requestId,
|
||||
"RESERVE-501", "ORG-501", "CNY", new BigDecimal("50000.00"), "WAITING_OWNERSHIP",
|
||||
Instant.parse("2026-08-20T01:30:00Z"), "user-1", "operator", Collections.emptyList());
|
||||
when(productions.findByRequestId(requestId)).thenReturn(Optional.of(batch));
|
||||
|
||||
assertThatThrownBy(() -> service.reverse("RESERVE-501", new IssuanceAuditActor("user-2", "reviewer")))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("already been produced");
|
||||
verify(accounts, never()).updateReserveBalance(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.any(BigDecimal.class));
|
||||
verify(executions, never()).save(org.mockito.ArgumentMatchers.any(ReserveDeductionExecution.class));
|
||||
}
|
||||
|
||||
private ReserveDeductionExecution execution(UUID requestId) {
|
||||
return new ReserveDeductionExecution(requestId, "RESERVE-501", new BigDecimal("200000.00"),
|
||||
new BigDecimal("50000.00"), new BigDecimal("150000.00"), "DEDUCTED",
|
||||
Instant.parse("2026-08-20T01:00:00Z"), "user-1", "operator");
|
||||
}
|
||||
|
||||
private ReserveDeductionNotification notification(UUID requestId) {
|
||||
return new ReserveDeductionNotification(requestId, "RESERVE-501", "BANK-501", "ORG-501",
|
||||
"ACCOUNT-501", "Bank 501 reserve", "CNY", new BigDecimal("50000.00"), "SUBMITTED",
|
||||
Instant.parse("2026-08-20T00:30:00Z"), "user-1", "operator");
|
||||
}
|
||||
|
||||
private CentralBankInstitutionAccount account(String balance) {
|
||||
return new CentralBankInstitutionAccount("BANK-501", "ORG-501", "ACCOUNT-501", "Bank 501 reserve",
|
||||
"NORMAL", LocalDate.parse("2020-01-01"), new BigDecimal(balance));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue