|
|
|
|
@ -17,8 +17,11 @@ import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptogr
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionSm2KeyPair;
|
|
|
|
|
import com.yau.digitalrmb.testsupport.WalletOpeningTestData;
|
|
|
|
|
import com.yau.digitalrmb.shared.wallet.WalletPrerequisiteProjectionService;
|
|
|
|
|
import com.yau.digitalrmb.exchange.application.service.ExchangeApplicationService;
|
|
|
|
|
import com.yau.digitalrmb.exchange.domain.model.ExchangeActor;
|
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
|
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
|
|
|
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
|
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
|
|
|
@ -35,6 +38,7 @@ class ExchangeControllerTest {
|
|
|
|
|
@Autowired private InstitutionIdentityCryptography cryptography;
|
|
|
|
|
@Autowired private InstitutionKeyService keyService;
|
|
|
|
|
@Autowired private WalletPrerequisiteProjectionService walletProjection;
|
|
|
|
|
@Autowired private ExchangeApplicationService exchangeService;
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
void seedPrototypePrerequisites() {
|
|
|
|
|
@ -187,6 +191,12 @@ class ExchangeControllerTest {
|
|
|
|
|
java.math.BigDecimal.class, accountId)).isEqualByComparingTo("0.00");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT status FROM exchange_debit_record WHERE exchange_id=?",
|
|
|
|
|
String.class, id)).isEqualTo("SETTLED");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT status FROM training_experiment_attempt WHERE business_id=?",
|
|
|
|
|
String.class, id)).isEqualTo("COMPLETED");
|
|
|
|
|
String attemptId = jdbc.queryForObject("SELECT id FROM training_experiment_attempt WHERE business_id=?",
|
|
|
|
|
String.class, id);
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchange/attempts/{id}/cancel", attemptId).with(user()))
|
|
|
|
|
.andExpect(status().isBadRequest());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@ -205,6 +215,153 @@ class ExchangeControllerTest {
|
|
|
|
|
.isZero();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void releasesDebitHoldAndReturnsToStepFourWhenCoinReservationLosesTheRace() throws Exception {
|
|
|
|
|
String[] values = createHeldExchange();
|
|
|
|
|
String attemptId = values[0];
|
|
|
|
|
String exchangeId = values[1];
|
|
|
|
|
String accountId = values[2];
|
|
|
|
|
|
|
|
|
|
jdbc.update("UPDATE central_bank_currency_ownership SET status='PAYMENT_LOCKED'," +
|
|
|
|
|
"lock_business_type='PAYMENT',lock_business_id='COMPETING_PAYMENT' " +
|
|
|
|
|
"WHERE owner_type='BANK' AND owner_id='BKCHCNBJ00001'");
|
|
|
|
|
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchange/attempts/{id}/steps/05/actions/receive-debit-voucher", attemptId)
|
|
|
|
|
.with(user()).contentType(MediaType.APPLICATION_JSON).content("{}"))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.actionStatus").value("COMPLETED"));
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchange/attempts/{id}/steps/05/actions/reserve-coins", attemptId)
|
|
|
|
|
.with(user()).contentType(MediaType.APPLICATION_JSON).content("{}"))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.actionStatus").value("ROLLED_BACK"))
|
|
|
|
|
.andExpect(jsonPath("$.data.currentStepCode").value("04"))
|
|
|
|
|
.andExpect(jsonPath("$.data.currentActionCode").value("execute-debit-hold"));
|
|
|
|
|
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT frozen_amount FROM simulated_bank_account WHERE account_id=?",
|
|
|
|
|
java.math.BigDecimal.class, accountId)).isEqualByComparingTo("0.00");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT status FROM exchange_debit_record WHERE exchange_id=?",
|
|
|
|
|
String.class, exchangeId)).isEqualTo("RELEASED");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT status FROM training_experiment_action WHERE attempt_id=? " +
|
|
|
|
|
"AND step_code='04' AND action_code='execute-debit-hold'", String.class, attemptId))
|
|
|
|
|
.isEqualTo("ROLLED_BACK");
|
|
|
|
|
|
|
|
|
|
jdbc.update("UPDATE central_bank_currency_ownership SET status='AVAILABLE',lock_business_type=NULL," +
|
|
|
|
|
"lock_business_id=NULL WHERE owner_type='BANK' AND owner_id='BKCHCNBJ00001'");
|
|
|
|
|
executeAction(attemptId, "04", "execute-debit-hold", "COMPLETED");
|
|
|
|
|
executeAction(attemptId, "04", "output-debit-voucher", "COMPLETED");
|
|
|
|
|
executeAction(attemptId, "05", "receive-debit-voucher", "COMPLETED");
|
|
|
|
|
executeAction(attemptId, "05", "reserve-coins", "COMPLETED");
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchanges/{id}/confirm-ownership", exchangeId).with(user()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.status").value("SUCCESS"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void cancellingAnExchangeAttemptReleasesTheDebitHold() throws Exception {
|
|
|
|
|
String[] values = createHeldExchange();
|
|
|
|
|
String attemptId = values[0];
|
|
|
|
|
String exchangeId = values[1];
|
|
|
|
|
String accountId = values[2];
|
|
|
|
|
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchange/attempts/{id}/cancel", attemptId).with(user()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.attemptStatus").value("CANCELLED"));
|
|
|
|
|
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT frozen_amount FROM simulated_bank_account WHERE account_id=?",
|
|
|
|
|
java.math.BigDecimal.class, accountId)).isEqualByComparingTo("0.00");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT status FROM exchange_debit_record WHERE exchange_id=?",
|
|
|
|
|
String.class, exchangeId)).isEqualTo("RELEASED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void cancellingAfterCoinReservationReleasesBothCoinLocksAndDebitHold() throws Exception {
|
|
|
|
|
String[] values = createHeldExchange();
|
|
|
|
|
String attemptId = values[0];
|
|
|
|
|
String exchangeId = values[1];
|
|
|
|
|
String accountId = values[2];
|
|
|
|
|
executeAction(attemptId, "05", "receive-debit-voucher", "COMPLETED");
|
|
|
|
|
executeAction(attemptId, "05", "reserve-coins", "COMPLETED");
|
|
|
|
|
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchange/attempts/{id}/cancel", attemptId).with(user()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.attemptStatus").value("CANCELLED"));
|
|
|
|
|
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT frozen_amount FROM simulated_bank_account WHERE account_id=?",
|
|
|
|
|
java.math.BigDecimal.class, accountId)).isEqualByComparingTo("0.00");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT status FROM exchange_debit_record WHERE exchange_id=?",
|
|
|
|
|
String.class, exchangeId)).isEqualTo("RELEASED");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM exchange_coin_reservation WHERE exchange_id=?",
|
|
|
|
|
Integer.class, exchangeId)).isZero();
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM central_bank_currency_ownership " +
|
|
|
|
|
"WHERE owner_type='BANK' AND owner_id='BKCHCNBJ00001' AND status='AVAILABLE' " +
|
|
|
|
|
"AND lock_business_type IS NULL AND lock_business_id IS NULL", Integer.class))
|
|
|
|
|
.isEqualTo(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void rollsBackBankSettlementWhenOwnershipTransferCannotBeRecorded() throws Exception {
|
|
|
|
|
String[] values = createHeldExchange();
|
|
|
|
|
String attemptId = values[0];
|
|
|
|
|
String exchangeId = values[1];
|
|
|
|
|
String accountId = values[2];
|
|
|
|
|
executeAction(attemptId, "05", "receive-debit-voucher", "COMPLETED");
|
|
|
|
|
executeAction(attemptId, "05", "reserve-coins", "COMPLETED");
|
|
|
|
|
jdbc.update("INSERT INTO currency_ownership_transfer (transaction_id,exchange_id,from_owner,to_wallet_id," +
|
|
|
|
|
"amount,coin_count,confirmation_digest,central_bank_signature,status,confirmed_at,confirmed_by) " +
|
|
|
|
|
"VALUES ('BLOCKING_TRANSFER',?,'BKCHCNBJ00001','WALLET_EXCHANGE_TEST',200,2,?,?,'CONFIRMED'," +
|
|
|
|
|
"CURRENT_TIMESTAMP,'test')", exchangeId, repeat('A', 64), "BLOCKING_SIGNATURE");
|
|
|
|
|
|
|
|
|
|
assertThatThrownBy(() -> exchangeService.confirmOwnership(java.util.UUID.fromString(exchangeId),
|
|
|
|
|
new ExchangeActor(USER_ID, "test", 1999L, 2999L))).isInstanceOf(RuntimeException.class);
|
|
|
|
|
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT balance FROM simulated_bank_account WHERE account_id=?",
|
|
|
|
|
java.math.BigDecimal.class, accountId)).isEqualByComparingTo("50000.00");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT frozen_amount FROM simulated_bank_account WHERE account_id=?",
|
|
|
|
|
java.math.BigDecimal.class, accountId)).isEqualByComparingTo("200.00");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT balance FROM digital_wallet WHERE wallet_id='WALLET_EXCHANGE_TEST'",
|
|
|
|
|
java.math.BigDecimal.class)).isEqualByComparingTo("0.00");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT status FROM exchange_debit_record WHERE exchange_id=?",
|
|
|
|
|
String.class, exchangeId)).isEqualTo("FROZEN");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM central_bank_currency_ownership " +
|
|
|
|
|
"WHERE status='EXCHANGE_LOCKED' AND lock_business_id=?", Integer.class, exchangeId))
|
|
|
|
|
.isEqualTo(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String[] createHeldExchange() throws Exception {
|
|
|
|
|
String context = mockMvc.perform(get("/api/v1/exchanges/context").with(user()))
|
|
|
|
|
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
|
|
|
|
|
String walletId = JsonPath.read(context, "$.data.walletId");
|
|
|
|
|
String accountId = JsonPath.read(context, "$.data.bankAccountId");
|
|
|
|
|
String body = "{\"walletId\":\"" + walletId + "\",\"bankAccountId\":\"" + accountId
|
|
|
|
|
+ "\",\"amount\":200.00}";
|
|
|
|
|
String created = mockMvc.perform(post("/api/v1/exchanges").with(user())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON).content(body))
|
|
|
|
|
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
|
|
|
|
|
String exchangeId = JsonPath.read(created, "$.data.id");
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchanges/{id}/sign", exchangeId).with(user()))
|
|
|
|
|
.andExpect(status().isOk());
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchanges/{id}/bank-process", exchangeId).with(user()))
|
|
|
|
|
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("FUNDS_HELD"));
|
|
|
|
|
String attempt = mockMvc.perform(get("/api/v1/exchange/attempts/current").with(user()))
|
|
|
|
|
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
|
|
|
|
|
String attemptId = JsonPath.read(attempt, "$.data.attemptId");
|
|
|
|
|
return new String[]{attemptId, exchangeId, accountId};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void executeAction(String attemptId, String step, String action, String expectedStatus) throws Exception {
|
|
|
|
|
mockMvc.perform(post("/api/v1/exchange/attempts/{id}/steps/{step}/actions/{action}",
|
|
|
|
|
attemptId, step, action).with(user())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON).content("{}"))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.actionStatus").value(expectedStatus));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String repeat(char value, int count) {
|
|
|
|
|
StringBuilder text = new StringBuilder(count);
|
|
|
|
|
for (int i = 0; i < count; i++) text.append(value);
|
|
|
|
|
return text.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private org.springframework.test.web.servlet.request.RequestPostProcessor user() {
|
|
|
|
|
return jwt().jwt(jwt -> jwt.subject(USER_ID).claim("userId", USER_ID).claim("preferred_username", "tzs001"));
|
|
|
|
|
}
|
|
|
|
|
|