|
|
@@ -0,0 +1,97 @@
|
|
|
+package com.payment.platform.module.payment.leshouying.entity;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
|
|
+import com.fasterxml.jackson.databind.SerializationFeature;
|
|
|
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import java.util.TimeZone;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 回调审计行的**上游原文不得随响应体出网**。
|
|
|
+ *
|
|
|
+ * <p>【要修的是什么】管理端订单抽屉的「回调轨迹」曾把 {@code raw_body} 原样铺在一个
|
|
|
+ * {@code <pre>} 里,于是页面上出现的是上游自己的字段名({@code alipay_order_no} /
|
|
|
+ * {@code trade_date} / {@code actual_money} …)和一份 JSON。用户 2026-09-30 裁定:
|
|
|
+ * 这些是上游的信息,不该到使用者眼前。
|
|
|
+ *
|
|
|
+ * <p>【为什么钉子下在实体上,而不是只删前端那一行】报文是随**响应体**进浏览器的 ——
|
|
|
+ * 前端不渲染不等于拿不到(F12 一看就有),且下一个人「顺手补个折叠面板」就又放出去了。
|
|
|
+ * 堵在序列化层,这条通道**结构上**不存在。所以本用例断言的是**真跑一遍 Jackson 出来的 JSON**,
|
|
|
+ * 不是「注解在场」这种代理指标。
|
|
|
+ *
|
|
|
+ * <p>【量具的诚实说明】线上那支 mapper 是 Spring 容器里由 {@code JacksonConfig} 建的那个 Bean。
|
|
|
+ * 本类构造的是一份按 {@code JacksonConfig:20-24} **逐行配的等价物**(SNAKE_CASE + JavaTimeModule
|
|
|
+ * + 关时间戳 + 东八区),**不是同一个实例**。这不影响本用例的结论:{@code @JsonIgnore} 是
|
|
|
+ * 注解级行为,与 mapper 的命名策略、时间模块都无关 —— 但它决定了「出网的键叫什么」
|
|
|
+ * ({@code rawBody} 还是 {@code raw_body}),而那正是要断言的东西,所以命名策略必须配成一样的。
|
|
|
+ */
|
|
|
+class LsyNotifyLogRawBodySerializationTest {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 一份**形态照抄真实报文**的原文:里面带上游的字段名与一个只在本用例出现的哨兵值。
|
|
|
+ * 哨兵是必需的 —— 只断言「键名 raw_body 不在」,会在「值被塞进别的键」时漏过去。
|
|
|
+ */
|
|
|
+ private static final String SENTINEL = "ZX9SENTINEL";
|
|
|
+ private static final String UPSTREAM_RAW_BODY =
|
|
|
+ "{\"return_code\":\"SUCCESS\",\"alipay_order_no\":\"2026093022001" + SENTINEL + "\","
|
|
|
+ + "\"actual_money\":\"1.00\",\"trade_date\":\"2026-09-30 12:00:00\"}";
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void rawBody_不出网_连键名都不存在() throws Exception {
|
|
|
+ LsyNotifyLogEntity log = new LsyNotifyLogEntity();
|
|
|
+ log.setRawBody(UPSTREAM_RAW_BODY);
|
|
|
+ log.setVerifyStatus("PASS");
|
|
|
+ log.setTradeType("1");
|
|
|
+
|
|
|
+ String json = mapper().writeValueAsString(log);
|
|
|
+ JsonNode node = mapper().readTree(json);
|
|
|
+
|
|
|
+ assertFalse(node.has("raw_body"),
|
|
|
+ "raw_body 这个键不该出现在响应体里(前端曾把它渲染成 <pre>)。实测 JSON=" + json);
|
|
|
+ assertFalse(json.contains(SENTINEL),
|
|
|
+ "原文里的**值**也不得从别的键漏出去 —— 只查键名会在字段改名后漏过去。实测 JSON=" + json);
|
|
|
+ assertFalse(json.contains("alipay_order_no"),
|
|
|
+ "上游的字段名本身就是不该给使用者看的信息(用户裁定的原话)。实测 JSON=" + json);
|
|
|
+ assertFalse(json.contains("actual_money"), "同上。实测 JSON=" + json);
|
|
|
+
|
|
|
+ // ★ 正对照:同一个对象的**其它**字段必须照常出网。
|
|
|
+ // 没有这一条,上面四个断言在「整个序列化坏掉 / 返回空对象」时也全绿 ——
|
|
|
+ // 那就成了一条永远通过、什么也没钉住的用例。
|
|
|
+ assertTrue(node.has("verify_status"),
|
|
|
+ "正对照:verify_status 必须照常出网,否则上面的「没有」是因为整个序列化没工作。实测 JSON=" + json);
|
|
|
+ assertEquals("PASS", node.get("verify_status").asText(),
|
|
|
+ "正对照必须是**真值**,不能只看键在不在。实测 JSON=" + json);
|
|
|
+ assertTrue(node.has("trade_type"), "正对照:trade_type 也要在。实测 JSON=" + json);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 值为 null 时同样不出网 —— 「有值才忽略」是两种不同的实现,
|
|
|
+ * 而 {@code @JsonIgnore} 是**无条件**的。这条钉住我们用的确实是后者
|
|
|
+ * (将来若有人换成 {@code @JsonInclude(NON_NULL)},raw_body 有值的那些行照样会漏出去)。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void rawBody_为null时也不出现() throws Exception {
|
|
|
+ LsyNotifyLogEntity log = new LsyNotifyLogEntity();
|
|
|
+ log.setVerifyStatus("FAIL");
|
|
|
+
|
|
|
+ JsonNode node = mapper().readTree(mapper().writeValueAsString(log));
|
|
|
+
|
|
|
+ assertFalse(node.has("raw_body"), "null 值同样不得出现 —— 忽略必须是无条件的。实测=" + node);
|
|
|
+ assertTrue(node.has("verify_status"), "正对照。实测=" + node);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 与 {@code JacksonConfig#objectMapper} 逐行等价的量具(不是容器里那一支实例,见类注释)。 */
|
|
|
+ private static ObjectMapper mapper() {
|
|
|
+ ObjectMapper mapper = new ObjectMapper();
|
|
|
+ mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
|
|
+ mapper.registerModule(new JavaTimeModule());
|
|
|
+ mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
|
|
+ mapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
|
|
+ return mapper;
|
|
|
+ }
|
|
|
+}
|