This commit is contained in:
2026-08-14 09:14:52 +08:00
parent 274a3f34c5
commit db80701534
8 changed files with 186 additions and 75 deletions
@@ -1,6 +1,7 @@
package com.baiyue.finance;
import com.baiyue.finance.service.FinanceService;
import com.baiyue.finance.service.FinanceVoucherValidator;
import com.baiyue.finance.client.FinanceFileClient;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
@@ -18,7 +19,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import org.springframework.test.util.ReflectionTestUtils;
class FinanceServiceValidationTest {
@Test
@@ -49,7 +49,7 @@ class FinanceServiceValidationTest {
@Test
void rejectsUnsupportedCostTypeBeforeWrite() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
FinanceService service = new FinanceService(jdbc, new ObjectMapper());
FinanceService service = service(jdbc);
var request = new FinanceService.CostRequest("C-1", "TICKET", 1L, null, null, null, null, "MANUAL", "拖车", BigDecimal.ONE, List.of("f"), null);
assertThrows(RuntimeException.class, () -> service.createCost(1L, 2L, request, "k1"));
verify(jdbc, never()).update(startsWith("INSERT INTO fin_cost"), (Object[]) any());
@@ -58,7 +58,7 @@ class FinanceServiceValidationTest {
@Test
void rejectsNonPositiveAllocation() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
FinanceService service = new FinanceService(jdbc, new ObjectMapper());
FinanceService service = service(jdbc);
var request = new FinanceService.ReceiptRequest("R-1", 1L, null, "BANK_TRANSFER", null, List.of("f"), null,
List.of(new FinanceService.ReceiptAllocation(10L, BigDecimal.ZERO)));
assertThrows(RuntimeException.class, () -> service.createReceipt(1L, 2L, request, "k2"));
@@ -67,7 +67,7 @@ class FinanceServiceValidationTest {
@Test
void rejectsDuplicateBillAllocationInsteadOfMerging() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
FinanceService service = new FinanceService(jdbc, new ObjectMapper());
FinanceService service = service(jdbc);
var request = new FinanceService.ReceiptRequest("R-2", 1L, null, "BANK_TRANSFER", null, List.of("f"), null,
List.of(new FinanceService.ReceiptAllocation(10L, BigDecimal.ONE), new FinanceService.ReceiptAllocation(10L, BigDecimal.ONE)));
assertThrows(RuntimeException.class, () -> service.createReceipt(1L, 2L, request, "k3"));
@@ -76,7 +76,8 @@ class FinanceServiceValidationTest {
@Test
void receiptFailsClosedWhenFileValidationCapabilityIsUnavailable() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
FinanceService service = new FinanceService(jdbc, new ObjectMapper());
FinanceService service = new FinanceService(jdbc, new ObjectMapper(),
new FinanceVoucherValidator(null, ""));
var request = new FinanceService.ReceiptRequest("R-FILE", 1L, null, "BANK_TRANSFER", null, List.of("77"), null,
List.of(new FinanceService.ReceiptAllocation(10L, BigDecimal.ONE)));
@@ -92,8 +93,7 @@ class FinanceServiceValidationTest {
FinanceFileClient files = mock(FinanceFileClient.class);
when(files.metadata(anyString(), eq(9L), eq(9L), eq(77L))).thenReturn(ApiResponse.success(
new FinanceFileClient.Metadata(77L, 10L, "FINANCE_VOUCHER", "9", "v.jpg", "sha", "image/jpeg", 10L)));
FinanceService service = new FinanceService(jdbc, new ObjectMapper().findAndRegisterModules());
ReflectionTestUtils.setField(service, "fileClient", files);
FinanceService service = service(jdbc, files);
var request = new FinanceService.ReceiptRequest("R-3", 1L, null, "BANK_TRANSFER", null, List.of("77"), null,
List.of(new FinanceService.ReceiptAllocation(10L, BigDecimal.ONE)));
assertThrows(RuntimeException.class, () -> service.createReceipt(9L, 2L, request, "k4"));
@@ -117,9 +117,7 @@ class FinanceServiceValidationTest {
holder.getKeyList().add(java.util.Map.of("GENERATED_KEY", 701L));
return 1;
}).when(jdbc).update(any(org.springframework.jdbc.core.PreparedStatementCreator.class), any(org.springframework.jdbc.support.KeyHolder.class));
FinanceService service = new FinanceService(jdbc, new ObjectMapper().findAndRegisterModules());
ReflectionTestUtils.setField(service, "fileClient", files);
ReflectionTestUtils.setField(service, "fileToken", "internal-token");
FinanceService service = service(jdbc, files);
var request = new FinanceService.ReceiptRequest("R-4", 1L, java.time.LocalDateTime.of(2026, 8, 11, 10, 0),
"BANK_TRANSFER", null, List.of("77"), "首笔收款",
List.of(new FinanceService.ReceiptAllocation(50L, new BigDecimal("40.00"))));
@@ -154,9 +152,7 @@ class FinanceServiceValidationTest {
holder.getKeyList().add(java.util.Map.of("GENERATED_KEY", 701L));
return 1;
}).when(jdbc).update(any(org.springframework.jdbc.core.PreparedStatementCreator.class), any(org.springframework.jdbc.support.KeyHolder.class));
FinanceService service = new FinanceService(jdbc, new ObjectMapper().findAndRegisterModules());
ReflectionTestUtils.setField(service, "fileClient", files);
ReflectionTestUtils.setField(service, "fileToken", "internal-token");
FinanceService service = service(jdbc, files);
var request = new FinanceService.ReceiptRequest("R-300", 1L, java.time.LocalDateTime.of(2026, 8, 11, 10, 0),
"BANK_TRANSFER", "BANK-300", List.of("77"), "三单收款", List.of(
new FinanceService.ReceiptAllocation(50L, new BigDecimal("100.00")),
@@ -176,7 +172,7 @@ class FinanceServiceValidationTest {
void rejectsSingleOrderWhenOrderNotOwnedByCustomer() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
when(jdbc.queryForObject(startsWith("SELECT COUNT(*) FROM fin_fee_source"), eq(Number.class), any(), any())).thenReturn(0);
FinanceService service = new FinanceService(jdbc, new ObjectMapper());
FinanceService service = service(jdbc);
var request = new FinanceService.CostRequest("C-2", "SINGLE_ORDER", 99L, null, null, null, null, "MANUAL", "货拉拉", BigDecimal.ONE, List.of("1"), null);
assertThrows(RuntimeException.class, () -> service.createCost(9L, 2L, request, "k5"));
}
@@ -198,9 +194,7 @@ class FinanceServiceValidationTest {
holder.getKeyList().add(java.util.Map.of("GENERATED_KEY", 701L));
return 1;
}).when(jdbc).update(any(org.springframework.jdbc.core.PreparedStatementCreator.class), any(org.springframework.jdbc.support.KeyHolder.class));
FinanceService service = new FinanceService(jdbc, new ObjectMapper());
ReflectionTestUtils.setField(service, "fileClient", files);
ReflectionTestUtils.setField(service, "fileToken", "internal-token");
FinanceService service = service(jdbc, files);
var request = new FinanceService.CostRequest("C-3", "CONTAINER_SHARED", null, null, 88L,
null, null, "MANUAL", "整柜成本", new BigDecimal("300.00"), List.of("77"), null);
var snapshot = List.of(new FinanceService.ContainerOrderVolume(
@@ -221,7 +215,7 @@ class FinanceServiceValidationTest {
.thenReturn(1);
var rows = List.<java.util.Map<String, Object>>of(java.util.Map.of("version", 2));
when(jdbc.queryForList(startsWith("SELECT id,bill_id,version"), eq(9L), eq(50L))).thenReturn(rows);
FinanceService service = new FinanceService(jdbc, new ObjectMapper());
FinanceService service = service(jdbc);
assertEquals(rows, service.billRevisions(9L, 50L));
}
@@ -231,9 +225,19 @@ class FinanceServiceValidationTest {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
when(jdbc.queryForObject(startsWith("SELECT COUNT(*) FROM fin_bill"), eq(Number.class), eq(50L), eq(9L)))
.thenReturn(0);
FinanceService service = new FinanceService(jdbc, new ObjectMapper());
FinanceService service = service(jdbc);
assertThrows(RuntimeException.class, () -> service.billRevisions(9L, 50L));
verify(jdbc, never()).queryForList(startsWith("SELECT id,bill_id,version"), (Object[]) any());
}
private static FinanceService service(JdbcTemplate jdbc, FinanceFileClient files) {
return new FinanceService(jdbc, new ObjectMapper().findAndRegisterModules(),
new FinanceVoucherValidator(files, "internal-token"));
}
private static FinanceService service(JdbcTemplate jdbc) {
return new FinanceService(jdbc, new ObjectMapper().findAndRegisterModules(),
mock(FinanceVoucherValidator.class));
}
}
@@ -1,6 +1,8 @@
package com.baiyue.finance;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.finance.client.FinanceFileClient;
import com.baiyue.finance.controller.FinanceSettlementController;
@@ -12,6 +14,7 @@ import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementSummary;
import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementActionRequest;
import com.baiyue.finance.service.FinanceEventException;
import com.baiyue.finance.service.FinanceSettlementService;
import com.baiyue.finance.service.FinanceVoucherValidator;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -136,8 +139,11 @@ class FinanceSettlementBehaviorTest {
when(fileClient.metadata("internal-token", 10L, 10L, 91L)).thenThrow(new IllegalStateException("down"));
FinanceSettlementService service = service(jdbc, fileClient);
assertCode(() -> service.createSettlementPayment(88L, 7L, "pay-key",
payment(new BigDecimal("40.00"), 0)), "FIN_503");
assertThatThrownBy(() -> service.createSettlementPayment(88L, 7L, "pay-key",
payment(new BigDecimal("40.00"), 0)))
.isInstanceOf(BizException.class)
.extracting(error -> ((BizException) error).getResultCode())
.isEqualTo(ResultCode.SERVICE_UNAVAILABLE);
verify(jdbc, never()).update(startsWith("INSERT INTO fin_settlement_payment"), (Object[]) any());
verify(jdbc, never()).update(startsWith("UPDATE fin_settlement SET paid_amount"), (Object[]) any());
@@ -516,10 +522,8 @@ class FinanceSettlementBehaviorTest {
}
private static FinanceSettlementService service(JdbcTemplate jdbc, FinanceFileClient fileClient) {
FinanceSettlementService service = new FinanceSettlementService(
jdbc, new ObjectMapper().findAndRegisterModules(), fileClient);
ReflectionTestUtils.setField(service, "fileToken", "internal-token");
return service;
return new FinanceSettlementService(jdbc, new ObjectMapper().findAndRegisterModules(),
new FinanceVoucherValidator(fileClient, "internal-token"));
}
private static CreateSettlementPaymentRequest payment(BigDecimal amount, int version) {
@@ -0,0 +1,87 @@
package com.baiyue.finance;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.finance.client.FinanceFileClient;
import com.baiyue.finance.service.FinanceVoucherValidator;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class FinanceVoucherValidatorTest {
@Test
void acceptsCustomerOwnedFinanceImage() {
FinanceFileClient files = mock(FinanceFileClient.class);
when(files.metadata("token", 9L, 9L, 77L)).thenReturn(ApiResponse.success(
new FinanceFileClient.Metadata(77L, 9L, "FINANCE_VOUCHER", "9", "receipt.jpg", "sha", "image/jpeg", 10L)));
new FinanceVoucherValidator(files, "token").validate(9L, List.of("77"), "财务凭证");
verify(files).metadata("token", 9L, 9L, 77L);
}
@Test
void rejectsMismatchedFileIdentityOwnershipTypeAndMime() {
FinanceFileClient files = mock(FinanceFileClient.class);
List<FinanceFileClient.Metadata> invalid = List.of(
new FinanceFileClient.Metadata(78L, 9L, "FINANCE_VOUCHER", "9", "receipt.jpg", "sha", "image/jpeg", 10L),
new FinanceFileClient.Metadata(77L, 10L, "FINANCE_VOUCHER", "9", "receipt.jpg", "sha", "image/jpeg", 10L),
new FinanceFileClient.Metadata(77L, 9L, "FINANCE_VOUCHER", "10", "receipt.jpg", "sha", "image/jpeg", 10L),
new FinanceFileClient.Metadata(77L, 9L, "OTHER", "9", "receipt.jpg", "sha", "image/jpeg", 10L),
new FinanceFileClient.Metadata(77L, 9L, "FINANCE_VOUCHER", "9", "receipt.pdf", "sha", "application/pdf", 10L));
for (FinanceFileClient.Metadata metadata : invalid) {
when(files.metadata(eq("token"), eq(9L), eq(9L), eq(77L))).thenReturn(ApiResponse.success(metadata));
assertThatThrownBy(() -> new FinanceVoucherValidator(files, "token")
.validate(9L, List.of("77"), "付款凭证"))
.isInstanceOf(BizException.class)
.extracting(error -> ((BizException) error).getResultCode()).isEqualTo(ResultCode.BAD_REQUEST);
}
}
@Test
void failsClosedWhenFileDependencyUnavailable() {
FinanceFileClient files = mock(FinanceFileClient.class);
when(files.metadata("token", 9L, 9L, 77L)).thenThrow(new IllegalStateException("down"));
assertThatThrownBy(() -> new FinanceVoucherValidator(files, "token")
.validate(9L, List.of("77"), "付款凭证"))
.isInstanceOf(BizException.class)
.extracting(error -> ((BizException) error).getResultCode()).isEqualTo(ResultCode.SERVICE_UNAVAILABLE);
}
@Test
void rejectsMissingInvalidAndDuplicateFileIds() {
FinanceVoucherValidator validator = new FinanceVoucherValidator(mock(FinanceFileClient.class), "token");
assertResult(() -> validator.validate(9L, List.of(), "财务凭证"), ResultCode.BAD_REQUEST);
assertResult(() -> validator.validate(9L, List.of("invalid"), "财务凭证"), ResultCode.BAD_REQUEST);
assertResult(() -> validator.validate(9L, List.of("77", "77"), "付款凭证"), ResultCode.BAD_REQUEST);
}
@Test
void rejectsWrongFileIdAndCustomerOwnership() {
FinanceFileClient files = mock(FinanceFileClient.class);
when(files.metadata("token", 9L, 9L, 77L)).thenReturn(ApiResponse.success(
new FinanceFileClient.Metadata(78L, 10L, "FINANCE_VOUCHER", "9", "receipt.jpg", "sha", "image/jpeg", 10L)));
assertResult(() -> new FinanceVoucherValidator(files, "token")
.validate(9L, List.of("77"), "付款凭证"), ResultCode.BAD_REQUEST);
}
@Test
void rejectsMissingToken() {
assertResult(() -> new FinanceVoucherValidator(mock(FinanceFileClient.class), "")
.validate(9L, List.of("77"), "财务凭证"), ResultCode.SERVICE_UNAVAILABLE);
}
private static void assertResult(org.assertj.core.api.ThrowableAssert.ThrowingCallable call,
ResultCode resultCode) {
assertThatThrownBy(call).isInstanceOf(BizException.class)
.extracting(error -> ((BizException) error).getResultCode()).isEqualTo(resultCode);
}
}