|
|
@@ -1,13 +1,20 @@
|
|
|
package com.payment.platform.module.payment.unified.controller;
|
|
|
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.payment.platform.common.config.JacksonConfig;
|
|
|
import com.payment.platform.core.security.JwtAuthFilter;
|
|
|
import com.payment.platform.core.security.SecurityConfig;
|
|
|
import com.payment.platform.core.security.TenantApiKeyAuthFilter;
|
|
|
import com.payment.platform.module.payment.unified.dto.vo.UnifiedH5OrderVO;
|
|
|
import com.payment.platform.module.payment.unified.entity.UnifiedOrderEntity;
|
|
|
+import com.payment.platform.module.payment.unified.service.UnifiedH5Service;
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.mock.web.MockHttpServletRequest;
|
|
|
import org.springframework.security.access.prepost.PreAuthorize;
|
|
|
+import org.springframework.test.web.servlet.MockMvc;
|
|
|
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
@@ -15,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
import java.lang.reflect.Field;
|
|
|
import java.lang.reflect.Modifier;
|
|
|
import java.util.Arrays;
|
|
|
+import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Set;
|
|
|
@@ -25,6 +33,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
|
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
|
|
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
|
|
|
/**
|
|
|
* 公开 H5 端点(Task 24)的契约与认证面。
|
|
|
@@ -120,7 +132,7 @@ class UnifiedH5ControllerTest {
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void orderVo_serialization_leaksNoInternalFields() throws Exception {
|
|
|
+ void orderVo_serialization_hasExactlyAllowedSnakeCaseKeys() throws Exception {
|
|
|
UnifiedOrderEntity entity = new UnifiedOrderEntity();
|
|
|
entity.setId(1234567890123456789L);
|
|
|
entity.setTenantId(2L);
|
|
|
@@ -132,8 +144,18 @@ class UnifiedH5ControllerTest {
|
|
|
entity.setSubject("测试订单");
|
|
|
entity.setTradeStatus("0");
|
|
|
|
|
|
- String json = new ObjectMapper()
|
|
|
- .writeValueAsString(UnifiedH5OrderVO.open(entity, "tok"));
|
|
|
+ // 生产 mapper(全局 SNAKE_CASE + JavaTimeModule),不是裸 ObjectMapper
|
|
|
+ ObjectMapper mapper = new JacksonConfig().objectMapper();
|
|
|
+ String json = mapper.writeValueAsString(UnifiedH5OrderVO.open(entity, "tok"));
|
|
|
+ JsonNode node = mapper.readTree(json);
|
|
|
+
|
|
|
+ Set<String> actual = new HashSet<>();
|
|
|
+ node.fieldNames().forEachRemaining(actual::add);
|
|
|
+ assertEquals(Set.of(
|
|
|
+ "order_no", "token", "out_trade_no", "mch_no", "way_code", "amount",
|
|
|
+ "subject", "trade_status", "pay_data_type", "pay_data",
|
|
|
+ "err_code", "err_msg", "created_time"),
|
|
|
+ actual, "H5 视图序列化键必须恰好是 13 个 snake_case 契约键: " + json);
|
|
|
|
|
|
assertFalse(json.contains("\"id\""), "不得暴露内部主键: " + json);
|
|
|
assertFalse(json.contains("tenant_id"), "不得暴露租户归属: " + json);
|
|
|
@@ -142,6 +164,25 @@ class UnifiedH5ControllerTest {
|
|
|
assertFalse(json.contains("appSecret"), "不得暴露密钥: " + json);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 对象值的未知键(例如 {@code {"channel_extra":{"a":1}}})在 Jackson 反序列化
|
|
|
+ * {@code Map<String,String>} 时就失败,根本走不到服务层的 ALLOWED_KEYS。
|
|
|
+ * 控制器必须把它翻成与参数错误同一应答(400 + 同一句话),而不是全局兜底的 500。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void objectValuedKey_is400_withIncompleteMessage_not500() throws Exception {
|
|
|
+ UnifiedH5Controller controller = new UnifiedH5Controller(
|
|
|
+ mock(UnifiedH5Service.class), new MockHttpServletRequest());
|
|
|
+ MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
|
|
|
+
|
|
|
+ mockMvc.perform(post(PREFIX + "/order")
|
|
|
+ .contentType(MediaType.APPLICATION_JSON)
|
|
|
+ .content("{\"mch_no\":\"M001\",\"channel_extra\":{\"a\":1}}"))
|
|
|
+ .andExpect(status().isBadRequest())
|
|
|
+ .andExpect(jsonPath("$.code").value(400))
|
|
|
+ .andExpect(jsonPath("$.msg").value("支付链接参数不完整"));
|
|
|
+ }
|
|
|
+
|
|
|
private static String[] readStringArray(Class<?> owner, String fieldName) throws Exception {
|
|
|
Field f = owner.getDeclaredField(fieldName);
|
|
|
f.setAccessible(true);
|