test(finance): cover overseas fee replay and frozen lifecycle

This commit is contained in:
2026-09-09 10:20:56 +08:00
parent 87d6469f02
commit 5ecee65720
@@ -164,4 +164,77 @@ class BusinessFeeSourceEventServiceTest {
null, base.traceId(), "LASTLEG:FEE:701:" + version, null, payload,
EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(payload)));
}
@Test
void sameEventIdWithDifferentPayloadIsRejectedWithoutWrites() {
var event = payableEvent("PUBLISHED", 1);
when(jdbc.queryForList(startsWith("SELECT payload_hash,status"), eq("LASTLEG"), eq(event.eventId())))
.thenReturn(List.of(Map.of("payload_hash", "different", "status", "PROCESSED", "result_resource_id", 901L)));
assertThatThrownBy(() -> service.consume(event)).hasMessageContaining("同一事件ID");
verify(jdbc, never()).update(anyString(), any(Object[].class));
}
@Test
void sameSourceVersionWithDifferentHashCannotOverwriteCost() {
var event = payableEvent("PUBLISHED", 1);
existingVersion("different", "AVAILABLE");
assertThatThrownBy(() -> service.consume(event)).hasMessageContaining("同一来源版本");
noFinancialWrites();
}
@Test
void voidedSourceCannotBeRevivedByPublicationReplay() {
var event = payableEvent("PUBLISHED", 1);
existingVersion(event.payload().get("sourceHash").asText(), "VOIDED");
assertThat(service.consume(event)).isEqualTo(901L);
noFinancialWrites();
}
@Test
void voidTargetsOnlyMatchingOverseasCostVersion() {
var event = payableEvent("VOIDED", 1);
existingVersion(event.payload().get("sourceHash").asText(), "AVAILABLE");
assertThat(service.consume(event)).isEqualTo(901L);
verify(jdbc).update(startsWith("UPDATE fin_cost SET status='VOIDED'"),
eq("LASTLEG"), eq("OVERSEAS_CONTAINER_FEE"), eq(701L), eq(1));
verify(jdbc, never()).update(startsWith("INSERT INTO fin_cost"), any(Object[].class));
}
@Test
void frozenSourceRejectsAdjustmentWithoutReplacingSourceOrCost() {
var event = payableEvent("ADJUSTED", 2);
when(jdbc.queryForObject(startsWith("SELECT COUNT(*) FROM fin_fee_source f WHERE f.source_system=?"),
eq(Integer.class), eq("LASTLEG"), eq("OVERSEAS_CONTAINER_FEE"), eq(701L))).thenReturn(1);
assertThatThrownBy(() -> service.consume(event)).hasMessageContaining("禁止调整或替代");
noFinancialWrites();
}
@Test
void frozenSourceRejectsVoidWithoutChangingSourceOrCost() {
var event = payableEvent("VOIDED", 1);
existingVersion(event.payload().get("sourceHash").asText(), "AVAILABLE");
when(jdbc.queryForObject(startsWith("SELECT COUNT(*) FROM fin_fee_source f WHERE f.id=?"),
eq(Integer.class), eq(901L))).thenReturn(1);
assertThatThrownBy(() -> service.consume(event)).hasMessageContaining("禁止作废");
noFinancialWrites();
}
private EventEnvelope payableEvent(String action, int version) {
when(suppliers.requireActive(71L, null)).thenReturn(
new com.baiyue.finance.client.WarehouseSupplierClient.SupplierSnapshot(71L, "供应商", null, "ACTIVE"));
return containerEvent("CNY", "61", action, version);
}
private void existingVersion(String hash, String status) {
when(jdbc.queryForList(startsWith("SELECT id,source_hash,status FROM fin_fee_source"),
eq("LASTLEG"), eq("OVERSEAS_CONTAINER_FEE"), eq(701L), eq(1)))
.thenReturn(List.of(Map.of("id", 901L, "source_hash", hash, "status", status)));
}
private void noFinancialWrites() {
verify(jdbc, never()).update(startsWith("INSERT INTO fin_fee_source"), any(Object[].class));
verify(jdbc, never()).update(startsWith("UPDATE fin_fee_source"), any(Object[].class));
verify(jdbc, never()).update(startsWith("INSERT INTO fin_cost"), any(Object[].class));
verify(jdbc, never()).update(startsWith("UPDATE fin_cost"), any(Object[].class));
}
}