fix: make issuance request transitions atomic

agent/payment-training-progress
chenyuan 4 weeks ago
parent 878c890171
commit e5e897ce7a

@ -49,27 +49,33 @@ public class IssuanceRequest {
public void prepareMessage(String requestTimestamp, String messageText) {
requireStatus(IssuanceRequestStatus.DRAFT);
this.requestTimestamp = Objects.requireNonNull(requestTimestamp, "requestTimestamp must not be null");
this.messageText = Objects.requireNonNull(messageText, "messageText must not be null");
String validatedRequestTimestamp = Objects.requireNonNull(requestTimestamp, "requestTimestamp must not be null");
String validatedMessageText = Objects.requireNonNull(messageText, "messageText must not be null");
this.requestTimestamp = validatedRequestTimestamp;
this.messageText = validatedMessageText;
this.status = IssuanceRequestStatus.MESSAGE_PREPARED;
}
public void recordDigest(String digest) {
requireStatus(IssuanceRequestStatus.MESSAGE_PREPARED);
this.digest = Objects.requireNonNull(digest, "digest must not be null");
String validatedDigest = Objects.requireNonNull(digest, "digest must not be null");
this.digest = validatedDigest;
this.status = IssuanceRequestStatus.DIGESTED;
}
public void recordSignature(String signingKeyRef, String signature) {
requireStatus(IssuanceRequestStatus.DIGESTED);
this.signingKeyRef = Objects.requireNonNull(signingKeyRef, "signingKeyRef must not be null");
this.signature = Objects.requireNonNull(signature, "signature must not be null");
String validatedSigningKeyRef = Objects.requireNonNull(signingKeyRef, "signingKeyRef must not be null");
String validatedSignature = Objects.requireNonNull(signature, "signature must not be null");
this.signingKeyRef = validatedSigningKeyRef;
this.signature = validatedSignature;
this.status = IssuanceRequestStatus.SIGNED;
}
public void packagePayload(String payloadJson) {
requireStatus(IssuanceRequestStatus.SIGNED);
this.payloadJson = Objects.requireNonNull(payloadJson, "payloadJson must not be null");
String validatedPayloadJson = Objects.requireNonNull(payloadJson, "payloadJson must not be null");
this.payloadJson = validatedPayloadJson;
this.status = IssuanceRequestStatus.PACKAGED;
}
@ -151,8 +157,9 @@ public class IssuanceRequest {
if (denominationTotal.compareTo(normalizedAmount) != 0) {
throw new IllegalArgumentException("denomination total must equal totalAmount");
}
String validatedCurrency = Objects.requireNonNull(currency, "currency must not be null");
this.totalAmount = normalizedAmount;
this.currency = Objects.requireNonNull(currency, "currency must not be null");
this.currency = validatedCurrency;
this.denominations = Collections.unmodifiableList(copiedDenominations);
}

@ -4,7 +4,9 @@ import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@ -96,12 +98,52 @@ class IssuanceRequestTest {
.isInstanceOf(IllegalStateException.class);
}
@Test
void rejectedDraftUpdateLeavesEveryObservableFieldUnchanged() {
IssuanceRequest request = draftRequest();
RequestSnapshot original = RequestSnapshot.from(request);
assertThatThrownBy(() -> request.updateDraft(
new BigDecimal("200.00"), null, Arrays.asList(new DenominationItem(100, 2))))
.isInstanceOf(NullPointerException.class);
original.assertMatches(request);
}
@Test
void rejectedMessagePreparationLeavesEveryObservableFieldUnchanged() {
IssuanceRequest request = draftRequest();
RequestSnapshot original = RequestSnapshot.from(request);
assertThatThrownBy(() -> request.prepareMessage("20260803120000", null))
.isInstanceOf(NullPointerException.class);
original.assertMatches(request);
}
@Test
void rejectedSignatureRecordingLeavesEveryObservableFieldUnchanged() {
IssuanceRequest request = draftRequest();
request.prepareMessage("20260803120000", "ISSUE|...");
request.recordDigest("0123456789ABCDEF");
RequestSnapshot original = RequestSnapshot.from(request);
assertThatThrownBy(() -> request.recordSignature("sm2-key-02", null))
.isInstanceOf(NullPointerException.class);
original.assertMatches(request);
}
private IssuanceRequest preparedSignedAndPackagedRequest() {
IssuanceRequest request = draftRequest();
request.prepareMessage("20260803120000", "ISSUE|BKCHCNBJ00001|ORG_3A4B5C6D7E8F|50000.00|100:400,50:200|DC|20260803120000");
assertThat(request.getStatus()).isEqualTo(IssuanceRequestStatus.MESSAGE_PREPARED);
request.recordDigest("0123456789ABCDEF");
assertThat(request.getStatus()).isEqualTo(IssuanceRequestStatus.DIGESTED);
request.recordSignature("sm2-key-02", "signature");
assertThat(request.getStatus()).isEqualTo(IssuanceRequestStatus.SIGNED);
request.packagePayload("{\"requestId\":\"b7f572a0-1e8d-4e3c-8e42-44b5994f0429\"}");
assertThat(request.getStatus()).isEqualTo(IssuanceRequestStatus.PACKAGED);
return request;
}
@ -114,4 +156,62 @@ class IssuanceRequestTest {
"DC",
Arrays.asList(new DenominationItem(100, 400), new DenominationItem(50, 200)));
}
private static final class RequestSnapshot {
private final IssuanceApplicationId id;
private final String bankCode;
private final String organizationId;
private final BigDecimal totalAmount;
private final String currency;
private final List<DenominationItem> denominations;
private final String requestTimestamp;
private final String messageText;
private final String digest;
private final String signature;
private final String signingKeyRef;
private final String payloadJson;
private final IssuanceRequestStatus status;
private final CentralBankReceiveStatus centralBankReceiveStatus;
private final Instant centralBankReceivedAt;
private RequestSnapshot(IssuanceRequest request) {
this.id = request.getId();
this.bankCode = request.getBankCode();
this.organizationId = request.getOrganizationId();
this.totalAmount = request.getTotalAmount();
this.currency = request.getCurrency();
this.denominations = new ArrayList<DenominationItem>(request.getDenominations());
this.requestTimestamp = request.getRequestTimestamp();
this.messageText = request.getMessageText();
this.digest = request.getDigest();
this.signature = request.getSignature();
this.signingKeyRef = request.getSigningKeyRef();
this.payloadJson = request.getPayloadJson();
this.status = request.getStatus();
this.centralBankReceiveStatus = request.getCentralBankReceiveStatus();
this.centralBankReceivedAt = request.getCentralBankReceivedAt();
}
static RequestSnapshot from(IssuanceRequest request) {
return new RequestSnapshot(request);
}
void assertMatches(IssuanceRequest request) {
assertThat(request.getId()).isEqualTo(id);
assertThat(request.getBankCode()).isEqualTo(bankCode);
assertThat(request.getOrganizationId()).isEqualTo(organizationId);
assertThat(request.getTotalAmount()).isEqualByComparingTo(totalAmount);
assertThat(request.getCurrency()).isEqualTo(currency);
assertThat(request.getDenominations()).containsExactlyElementsOf(denominations);
assertThat(request.getRequestTimestamp()).isEqualTo(requestTimestamp);
assertThat(request.getMessageText()).isEqualTo(messageText);
assertThat(request.getDigest()).isEqualTo(digest);
assertThat(request.getSignature()).isEqualTo(signature);
assertThat(request.getSigningKeyRef()).isEqualTo(signingKeyRef);
assertThat(request.getPayloadJson()).isEqualTo(payloadJson);
assertThat(request.getStatus()).isEqualTo(status);
assertThat(request.getCentralBankReceiveStatus()).isEqualTo(centralBankReceiveStatus);
assertThat(request.getCentralBankReceivedAt()).isEqualTo(centralBankReceivedAt);
}
}
}

Loading…
Cancel
Save