浏览代码

test(汇安融): 湖南/长沙 的真实回包钉成夹具 —— 省带「省」市不带「市」

用户 2026-10-08 从 ipwho.is 取了 118.248.202.107 的回包来核(他没带 lang ⇒ 回的是
英文 Hunan Sheng / Changsha)。当场复量同一条:
- 带 ?lang=zh-CN(代码里恒带,:157)⇒ region=湖南省、city=长沙;
- 不带 ⇒ 英文,按中文名匹配全线失效 —— 这条已由 lookup_asksForChineseNamesExplicitly
  钉住 URL 形态。

新增两条钉子(输入全部抄自实测回包,不是构造的):
- 解析器:IpGeo("CN","湖南省","HN","长沙") —— 上游省名带后缀、市名不带,两种习惯并存;
- 目录:湖南省 → 43、长沙 → 4301(与「长沙市」命中的是同一个地级)。

顺带订正类 javadoc 一句失实的话:夹具不只略去四个嵌套对象,borders 也没抄
(逐字段比对过线上回包),原文写「其余逐字未改」不成立。

读数:两个类 16/16、14/14(先删旧报告再跑,避免读成上一次的绿);
整包 1213/0/0/0(比上一轮 +1 = 新增的这条用例)。
alphaH 1 周之前
父节点
当前提交
2717f7937e

+ 28 - 3
java/src/test/java/com/payment/platform/module/payment/har/service/HarIpRegionResolverTest.java

@@ -20,9 +20,10 @@ import static org.mockito.Mockito.verifyNoInteractions;
 /**
  * IP 归属地解析器。
  *
- * <p>下面 5 段 JSON 是 2026-10-08 用 curl 从 {@code https://ipwho.is/{ip}?lang=zh-CN}
- * 抄回来的**真实回包**(末尾的 flag/connection/timezone/readme 四个嵌套对象原样略去,
- * 其余逐字未改);末一条是构造输入,已标注。用真实回包是因为这个解析器的全部工作
+ * <p>下面 6 段 JSON 是 2026-10-08 用 curl 从 {@code https://ipwho.is/{ip}?lang=zh-CN}
+ * 抄回来的**真实回包**(末尾的 flag/connection/timezone/readme 四个嵌套对象与 {@code borders}
+ * 字段略去 —— 2026-10-08 逐字段比对过线上回包,此处据实写成「也略去」;其余逐字未改);
+ * 末一条是构造输入,已标注。用真实回包是因为这个解析器的全部工作
  * 就是「上游到底怎么回」,自造的理想 JSON 会把真实坑(繁体「區」、区级 city 名)滤掉。
  */
 class HarIpRegionResolverTest {
@@ -34,6 +35,13 @@ class HarIpRegionResolverTest {
              "latitude":32.0616657,"longitude":118.7777759,"is_eu":false,"postal":"210000","calling_code":"86",
              "capital":"北京"}""";
 
+    /** 实测:118.248.202.107 → 湖南省 / **长沙**(用户 2026-10-08 从 ipwho.is 取回的同一个 IP) */
+    private static final String P_HN_CHANGSHA = """
+            {"ip":"118.248.202.107","success":true,"type":"IPv4","continent":"亚洲","continent_code":"AS",
+             "country":"中国","country_code":"CN","region":"湖南省","region_code":"HN","city":"长沙",
+             "latitude":28.1987397,"longitude":112.9708739,"is_eu":false,"postal":"410000","calling_code":"86",
+             "capital":"北京"}""";
+
     /** 实测:218.201.4.2 → 重庆市 / **渝中區**(区级名 + 繁体「區」) */
     private static final String P_CQ_YUZHONG = """
             {"ip":"218.201.4.2","success":true,"type":"IPv4","continent":"亚洲","continent_code":"AS",
@@ -98,6 +106,23 @@ class HarIpRegionResolverTest {
         assertEquals("南京", geo.cityName());
     }
 
+    /**
+     * 湖南省这条回包的 {@code city} 是「长沙」——**不带「市」后缀**(与「南京」「杭州」同形)。
+     *
+     * <p>钉它的理由:省名带后缀、市名不带,是上游两种不同的习惯;解析器这一层**不做任何规整**,
+     * 归一化在 {@code HarRegionCatalog}(那边有「长沙 → 4301」的用例)。这条一旦被人顺手 trim 或
+     * 补后缀,两边就有一处会红。
+     */
+    @Test
+    void lookup_parsesRealChangshaPayload() {
+        IpGeo geo = resolverReturning(P_HN_CHANGSHA).lookup("118.248.202.107").orElse(null);
+
+        assertEquals("CN", geo.countryCode());
+        assertEquals("湖南省", geo.regionName());
+        assertEquals("HN", geo.regionCode());
+        assertEquals("长沙", geo.cityName());
+    }
+
     /** ★ {@code ?lang=zh-CN} 是这套设计的前提:不带它上游回拼音/英文,按中文名匹配全线失效。 */
     @Test
     void lookup_asksForChineseNamesExplicitly() {

+ 4 - 0
java/src/test/java/com/payment/platform/module/payment/har/util/HarRegionCatalogTest.java

@@ -158,6 +158,7 @@ class HarRegionCatalogTest {
         assertEquals("11", provinceCode("北京市"));
         assertEquals("11", provinceCode("北京"));
         assertEquals("32", provinceCode("江苏省"));
+        assertEquals("43", provinceCode("湖南省"), "实测回包(118.248.202.107)");
     }
 
     @Test
@@ -178,6 +179,9 @@ class HarRegionCatalogTest {
         assertEquals("5401", cityCode("54", "拉萨"));
         assertEquals("5401", cityCode("54", "拉薩"), "同一条实测回包里 city 是简体,但别的字段出现过繁体");
         assertEquals("3202", cityCode("32", "無錫市"), "繁体整串也要能命中");
+        // 实测回包(118.248.202.107):省名带「省」、市名不带「市」——两侧都要能命中同一个地级
+        assertEquals("4301", cityCode("43", "长沙"), "实测回包 city 不带后缀");
+        assertEquals("4301", cityCode("43", "长沙市"));
     }
 
     @Test