fix: audit issuance writes with current account

agent/payment-training-progress
chenyuan 4 weeks ago
parent 34c3d01a65
commit 63960e107c

@ -31,3 +31,13 @@ Implemented Task 4 application commands, query views, commercial-bank orchestrat
- The repository's existing MyBatis audit handler derives `created_by` and `updated_by` from the authenticated Spring Security account; application operation signatures retain the authenticated account name expected by the next REST layer.
- JSON payload includes the required request identity, bank/organization fields, denomination rows, currency, timestamp, digest, and signature. `orgCode` and `bankCode` both carry the aggregate's commercial-bank code to support either naming convention at the integration boundary.
- No known Task 4 scope issues remain.
## Fix Round 1: Explicit Audit Actor
- Root cause: every commercial-bank write accepted `authenticatedAccountName` but discarded it before repository persistence, allowing the MyBatis audit handler to use its `SYSTEM` fallback whenever no Spring Security context was present.
- Added an actor-aware repository save boundary. The commercial-bank service now passes its supplied authenticated account for create, draft update, message preparation, digest, signature, packaging, and send. The MyBatis repository writes that actor to both audit columns on insert and only `updated_by` on later writes, preserving the original creator.
- Added a persistence regression that exercises all of those operations through the Spring application service and reads the actual `issuance_request` row after each operation.
- RED: `mvn '-Dtest=MybatisIssuanceRequestRepositoryTest' test -B` failed as expected with `expected: "creator" but was: "SYSTEM"`.
- GREEN: `mvn '-Dtest=CommercialBankIssuanceApplicationServiceTest,CentralBankIssuanceQueryServiceTest,MybatisIssuanceRequestRepositoryTest' test -B` passed with 10 tests and 0 failures/errors.
- JDK 8: `mvn test -B` using Temurin `1.8.0_502` passed with 46 tests and 0 failures/errors.
- `git diff --check` completed without whitespace errors before commit.

@ -64,7 +64,7 @@ public class CommercialBankIssuanceApplicationService {
IssuanceRequest request = IssuanceRequest.create(new IssuanceApplicationId(id), requestNo(id),
command.getBankCode(), command.getOrganizationId(), command.getTotalAmount(), command.getCurrency(),
command.getDenominations());
repository.save(request);
repository.save(request, auditActor(authenticatedAccountName));
return IssuanceRequestView.from(request);
} catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) {
throw validationError(exception);
@ -76,7 +76,7 @@ public class CommercialBankIssuanceApplicationService {
try {
IssuanceRequest request = requireRequest(id);
request.updateDraft(command.getTotalAmount(), command.getCurrency(), command.getDenominations());
repository.save(request);
repository.save(request, auditActor(authenticatedAccountName));
return IssuanceRequestView.from(request);
} catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) {
throw validationError(exception);
@ -91,7 +91,7 @@ public class CommercialBankIssuanceApplicationService {
String message = messageComposer.compose(request.getBankCode(), request.getOrganizationId(),
request.getTotalAmount(), request.getDenominations(), request.getCurrency(), timestamp);
request.prepareMessage(timestamp, message);
repository.save(request);
repository.save(request, auditActor(authenticatedAccountName));
return IssuanceRequestView.from(request);
} catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) {
throw validationError(exception);
@ -104,7 +104,7 @@ public class CommercialBankIssuanceApplicationService {
IssuanceRequest request = requireRequest(id);
SignedIssuancePayload signedPayload = signatureService.sign(SIGNING_KEY_REF, request.getMessageText());
request.recordDigest(signedPayload.getDigest());
repository.save(request);
repository.save(request, auditActor(authenticatedAccountName));
return IssuanceRequestView.from(request);
} catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) {
throw validationError(exception);
@ -117,7 +117,7 @@ public class CommercialBankIssuanceApplicationService {
IssuanceRequest request = requireRequest(id);
SignedIssuancePayload signedPayload = signatureService.sign(SIGNING_KEY_REF, request.getMessageText());
request.recordSignature(signedPayload.getSigningKeyRef(), signedPayload.getSignature());
repository.save(request);
repository.save(request, auditActor(authenticatedAccountName));
return IssuanceRequestView.from(request);
} catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) {
throw validationError(exception);
@ -129,7 +129,7 @@ public class CommercialBankIssuanceApplicationService {
try {
IssuanceRequest request = requireRequest(id);
request.packagePayload(payloadFor(request));
repository.save(request);
repository.save(request, auditActor(authenticatedAccountName));
return IssuanceRequestView.from(request);
} catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) {
throw validationError(exception);
@ -141,7 +141,7 @@ public class CommercialBankIssuanceApplicationService {
try {
IssuanceRequest request = requireRequest(id);
request.sendToCentralBank(Instant.now());
repository.save(request);
repository.save(request, auditActor(authenticatedAccountName));
return IssuanceRequestView.from(request);
} catch (IllegalArgumentException | IllegalStateException | NullPointerException exception) {
throw validationError(exception);
@ -207,6 +207,13 @@ public class CommercialBankIssuanceApplicationService {
return "ISSUE_REQ_" + id.toString().replace("-", "").toUpperCase(Locale.ROOT);
}
private static String auditActor(String authenticatedAccountName) {
if (authenticatedAccountName == null || authenticatedAccountName.trim().isEmpty()) {
throw new IllegalArgumentException("authenticated account name must not be blank");
}
return authenticatedAccountName;
}
private static BusinessException validationError(RuntimeException exception) {
return new BusinessException(ErrorCode.VALIDATION_ERROR, exception.getMessage());
}

@ -9,6 +9,10 @@ import java.util.Optional;
public interface IssuanceRequestRepository {
void save(IssuanceRequest request);
default void save(IssuanceRequest request, String auditActor) {
save(request);
}
Optional<IssuanceRequest> findById(IssuanceApplicationId id);
Optional<IssuanceBankInventory> findInventoryByBankCode(String bankCode);

@ -22,6 +22,7 @@ import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Objects;
import java.util.UUID;
@Repository
@ -41,11 +42,24 @@ public class MybatisIssuanceRequestRepository implements IssuanceRequestReposito
@Override
@Transactional
public void save(IssuanceRequest request) {
saveInternal(request, null);
}
@Override
@Transactional
public void save(IssuanceRequest request, String auditActor) {
saveInternal(request, Objects.requireNonNull(auditActor, "auditActor must not be null"));
}
private void saveInternal(IssuanceRequest request, String auditActor) {
String requestId = request.getId().value().toString();
IssuanceRequestEntity entity = toEntity(request);
if (requestMapper.selectById(requestId) == null) {
IssuanceRequestEntity existing = requestMapper.selectById(requestId);
if (existing == null) {
assignAuditActorForInsert(entity, auditActor);
requestMapper.insert(entity);
} else {
assignAuditActorForUpdate(entity, auditActor);
requestMapper.updateById(entity);
}
@ -56,6 +70,19 @@ public class MybatisIssuanceRequestRepository implements IssuanceRequestReposito
}
}
private void assignAuditActorForInsert(IssuanceRequestEntity entity, String auditActor) {
if (auditActor != null) {
entity.setCreatedBy(auditActor);
entity.setUpdatedBy(auditActor);
}
}
private void assignAuditActorForUpdate(IssuanceRequestEntity entity, String auditActor) {
if (auditActor != null) {
entity.setUpdatedBy(auditActor);
}
}
@Override
public Optional<IssuanceRequest> findById(IssuanceApplicationId id) {
String requestId = id.value().toString();

@ -1,5 +1,8 @@
package com.yau.digitalrmb.issuance.infrastructure.persistence;
import com.yau.digitalrmb.issuance.application.command.CreateIssuanceRequestCommand;
import com.yau.digitalrmb.issuance.application.command.UpdateIssuanceRequestCommand;
import com.yau.digitalrmb.issuance.application.service.CommercialBankIssuanceApplicationService;
import com.yau.digitalrmb.issuance.domain.model.CentralBankReceiveStatus;
import com.yau.digitalrmb.issuance.domain.model.DenominationItem;
import com.yau.digitalrmb.issuance.domain.model.IssuanceBankInventory;
@ -9,6 +12,7 @@ import com.yau.digitalrmb.issuance.domain.model.IssuanceRequestStatus;
import com.yau.digitalrmb.issuance.domain.repository.IssuanceRequestRepository;
import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceBankInventoryEntity;
import com.yau.digitalrmb.issuance.infrastructure.persistence.mapper.IssuanceBankInventoryMapper;
import com.yau.digitalrmb.issuance.infrastructure.persistence.mapper.IssuanceRequestMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@ -32,6 +36,12 @@ class MybatisIssuanceRequestRepositoryTest {
@Autowired
private IssuanceBankInventoryMapper inventoryMapper;
@Autowired
private IssuanceRequestMapper requestMapper;
@Autowired
private CommercialBankIssuanceApplicationService commercialBankService;
@Test
void savesAndRestoresTheRequestWithItsDenominationsAndCentralBankStatus() {
IssuanceRequest sentRequest = sentRequest();
@ -100,6 +110,42 @@ class MybatisIssuanceRequestRepositoryTest {
assertThat(restored.getDenominations()).containsExactly(new DenominationItem(100, 500));
}
@Test
void commercialBankWritesPersistTheSuppliedActorForCreateUpdateAndTransitions() {
UUID requestId = commercialBankService.create(createCommand(), "creator").getId();
assertAuditActor(requestId, "creator", "creator");
commercialBankService.update(requestId, updateCommand(), "updater");
assertAuditActor(requestId, "creator", "updater");
commercialBankService.prepareMessage(requestId, "preparer");
assertAuditActor(requestId, "creator", "preparer");
commercialBankService.digest(requestId, "digester");
assertAuditActor(requestId, "creator", "digester");
commercialBankService.sign(requestId, "signer");
assertAuditActor(requestId, "creator", "signer");
commercialBankService.packagePayload(requestId, "packager");
assertAuditActor(requestId, "creator", "packager");
commercialBankService.send(requestId, "sender");
assertAuditActor(requestId, "creator", "sender");
}
private CreateIssuanceRequestCommand createCommand() {
return new CreateIssuanceRequestCommand("BKCHCNBJ00001", "ORG_3A4B5C6D7E8F", new BigDecimal("50000.00"),
"DC", Arrays.asList(new DenominationItem(100, 400), new DenominationItem(50, 200)));
}
private UpdateIssuanceRequestCommand updateCommand() {
return new UpdateIssuanceRequestCommand(new BigDecimal("50000.00"), "DC",
Arrays.asList(new DenominationItem(100, 500)));
}
private void assertAuditActor(UUID requestId, String expectedCreatedBy, String expectedUpdatedBy) {
com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestEntity entity =
requestMapper.selectById(requestId.toString());
assertThat(entity.getCreatedBy()).isEqualTo(expectedCreatedBy);
assertThat(entity.getUpdatedBy()).isEqualTo(expectedUpdatedBy);
}
private IssuanceRequest sentRequest() {
UUID id = UUID.fromString("12345678-1234-1234-1234-1234567890ab");
IssuanceRequest request = IssuanceRequest.create(new IssuanceApplicationId(id),

Loading…
Cancel
Save