Files
lislgosms/docs/http-api-full-acceptance-20260914.md
hectorzhao ac6449028c
CSS quality / css-quality (push) Has been cancelled
docs: close HTTP API real simulator acceptance
2026-09-14 15:53:22 +08:00

21 KiB
Raw Permalink Blame History

HTTP接口全量真实模拟验收(2026-09-14)

更新:2026-09-14T07:49:14.156Z。本轮四个已确认代码缺陷均已修复、提交、推送、部署测试并真实复验;测试设施已收尾。本文最终结果替代此前阶段性状态,原失败请求保存在证据中。

交付范围与版本

  • 测试环境:100.93.204.60:12026。真实PG回读应用版本 97d1334423;无预生产发布。
  • 提交:92b112cc6e29a14d842e040a9d469fd717a95f96DNS lookup),a420d61b23e2ce527d4f8978bb36d7ca5825b6c7(参数边界),97d133442350b9725422ed4e55386f47e37004fd(小数秒兼容)。三者均推送;a420未单独部署,最终97包含全部修复。文档收尾另作仅文档提交,不改变线上应用版本。
  • 原main和其他会话metrics、tools/release、部署工具与文档草稿保留;不reset、不清理、不切分支,精确文件/hunk提交。本轮测试配置只作用于三个新应用。
  • 用户专项授权的真实模拟:仅现有6条LGST-M/U/T主备通道,虚构号段,最多30条独立短信、常规每秒不超过1条、最多160个报文;本轮实际20条/23个Submit。无真实运营商、历史重入队、余额直接修改或旧客户/通道配置变更。

缺陷与根因

编号 只读复现/根因 最小修复与最终验证
B01/P1 真实Webhook在发送网络请求前报Invalid IP address: undefinedNode自动地址族选择传all=true,旧lookup仍回单地址 按all返回固定地址数组,保留SSRF和DNS固定;真实HTTP连接回归、实际公网HTTPS签名/成功/重试验收通过
B02/P2 https://[::1]/hook返回500URL.hostname含括号错误进入DNS查询 先规范化IPv6字面量,DNS失败稳定400;映射IPv4规范化后执行私网检查;线上回环/ULA/link-local/mapped私网400且原配置不变
B03/P2 2026-02-30被Date自动换算,查询返回200 严格日历/闰年/时分秒/时区校验,query和cursor一致;非法400,合法闰日、日期、时区及高精度小数秒200
B04/P2 body-parser阶段的畸形/非对象JSON返回通用错误,缺少公开code/关联ID 只在公开SMS路径处理已知parser错误;真实畸形/primitive400、3MiB413、未知字符集/编码415,固定安全正文、problem契约和X-Request-Id一致

DNS失败400及公网IPv6地址分类有定向单测;未将私网检查声明为全部特殊用途网段治理。新增@types/express仅开发类型依赖,无运行时依赖升级、迁移、计费/发送规则改变。

自动与真实验收

  • 精确最终候选74套 809项 全通过,格式、Lint、TypeScript、远端实际API构建通过;security:verify通过。之前工作区783与精确92候选782的差异来自受保护metrics额外测试,不夹带。中间mock DNS spy配置失败已改为模块mock,原失败日志保留;不将失败重试时间算停机。
  • 235条请求/断言记录:229条通过,6条原始非通过保留。其中2条是已修复后通过的新旧缺陷对照;4条为契约预期/批准模板前置/约4秒服务器时差/测试断言路径错误,逐项见下表。不宣称数学意义上的所有参数组合均已穷举。
  • 四公开接口覆盖认证缺失/错误、timestamp/nonce、过期/撤销、IP、能力开关/QPS、手机号/正文/客户编号/幂等键、类型长度边界、同键重放冲突和5请求并发、日期/cursor/limit、同企业跨应用/跨企业隔离。
  • 真实Chrome 152.0.7977.83加载公开文档200、无pageerrorMD和OpenAPI JSON可读,413/415声明存在。原浏览器连接工具报nodeRepl.fetch request failed,改用已安装Chrome独立无头会话;未控制/关闭用户浏览器。没有UI/CSS代码改动。

账务、CMPP与上行

20条短信:19送达、1预期UNDELIV失败并退款;3网、长短信3段、UTF8换行转义均覆盖。23个实际CMPP Submit(含长短信分段及失败短信备用模拟路由),PG21条发送待办published、20条HTTP待办dispatched。失败短信2次供应商尝试未重复收取客户费用;成功计费21单位×325=6825,余额 1848101→1841276,冻结/解冻/扣退款流水与最终记录一致。

7条实际CMPP上行:共享接入号3条ambiguous隐藏,独立接入号4条matched;列表、同时间分页、过滤、详情、客户自身字段及租户隔离通过。匹配上行4条下游ACK后delivered;发布重启后测试CMPP客户端重新连接才收到最后1条,未重新入队。详情不暴露通道、供应商或内部匹配字段。

Webhook完整场景

实际24事件(20回执、4上行):22 delivered、2预设failed302不跟随、持续503耗尽);受控接收端31次真实HTTPS请求。HTTP 200/204成功;400终止;302不跟随;408/429/500/503第一次失败后至少60秒退避,再次200;10秒超时、15秒延迟响应后重试;最大2次停止;人工重试本轮400失败成功、已完成再次重试409、跨应用404。逐次原始体HMAC有效,eventId/体摘要稳定、重试时间戳更新;本轮receipt密钥轮换后新事件首投成功。

初始12个本轮旧失败事件在网络路径恢复后全部真实送达,未把旧Invalid-IP、超时或TLS断开记录删除。接收端按测试事件模拟HTTP状态,业务消息、队列、PG和CMPP均使用真实服务。

原始非通过记录

记录 澄清/闭环
unknown_internal_channel_field 预期400与未禁止附加字段契约不一致,send关闭实际403且无发送;不是确认缺陷
normal_mobile 缺少批准模板导致422;补齐本轮fixture后202/送达
expired_credential 本机与服务器约4秒时差;超过服务器期限后401
webhook_url_ipv6_loopback B02原500;最终线上400且配置不变
nonexistent_calendar_date B03原200;最终线上400
postdeploy_rotated_webhook 断言误读event.messageId,应为event.data.messageId;实际首投delivered、签名有效,修正断言通过

基础设施与测试收尾

最终PG记录及实际版本、三Redis Streamsgateway.submit.commands/results、gateway.protocol.logspending/lag均0API/Gateway/PostgreSQL/Redis active。真实服务名cmpp-minio active/running、/minio/health/live200;最初查询minio无此unit不代表服务故障。标准发布verify核查服务、日志增量、迁移、保护文件和队列,verificationWarnings为空。

测试机代理DNS把临时Cloudflare域名解析成198.18.1.78,普通HTTPS约5秒TLS断开;固定公开解析IP且保留TLS主机校验200。用户批准唯一域名临时hosts映射,约15:02建立,15:30重置30分钟独立自动清理;15:45:58主动精确移除,原hosts SHA256 adf2b112dcb2175c2b8013aed75ebd1b0e60d0d90f5616fad9add6740f22c6d3完全恢复,timer inactive。没有改默认网关、DNS服务或OpenWrt。备用webhook.site临时接收端创建后删除204,无业务事件。

三个新应用已disabled、HTTP关闭、active凭据0,待办0;测试supplier、CMPP客户端、HTTP receiver、Cloudflared及独立浏览器均关闭。原用户Tailscale17900转发保留。测试应用、模板、签名、消息、回执、上行、账务与审计保留,不删除业务历史。

发布、恢复资产和容量

最终标准计划20260914T072054-97d133442350-766123b9plan→validate→preflight→prepare→deploy(含verify)通过。工具为受保护未提交tools/release,计划绑定完整工具摘要,不以应用SHA冒充工具版本。当前版本97在/opt/cmpp-platform,上一有效92的previous-api-dist、previous-api-node_modules、previous-source位于/opt/cmpp-releases/20260914T072054-97d133442350-766123b9/。

阶段 92部署秒 97最终部署秒
远端prepare 40.3 48.6
独立备份 35.0 34.8
stop 2.8 1.7
start 5.5 5.5
verify 4.1 4.2

最终本地候选API测试142.092秒,依赖安装/其他门禁另记validation时间;实际本轮业务验收时间见results.json startedAt/completedAt。故障修复、网络等待、浏览器诊断与文档整理均不计服务停机。两次无DB迁移。恢复资产:

  • /var/backups/cmpp-platform/20260914T060412-92b112cc6e29-35305dbd-attempt-1789366449273005177
  • /var/backups/cmpp-platform/20260914T072054-97d133442350-766123b9-attempt-1789371103065630279

PG/Redis/源码/配置独立归档摘要与可读性通过,未实际恢复演练。current/previous/candidate/source/冷副本路径逐项见capacity-final.json;本轮没有迁盘冷副本或数据盘。测试机全部/dev/sda2;可用22,837,911,552→21,341,405,184→19,834,384,384字节(最终81%已用)。

路径 92发布前字节 92发布后字节 97发布后字节 97阶段新增
/opt/cmpp-platform 1027432448 1027469312 1027747840 278528
/opt/cmpp-releases 8527847424 9521917952 10516475904 994557952
/var/backups/cmpp-platform 5556809728 6053670912 6550622208 496951296
/var/log 963973120 964083712 964415488 331776
/root/.npm 646717440 646819840 671383552 24563712

最终发布目录独立994,557,952字节、独立备份496,951,296字节;上一92独立发布目录994,070,528/备份496,861,184字节。更早历史资产保留,未获清理范围、不使用临时删除脚本,容量治理未完成。

未执行与证据边界

未连接真实运营商、未做压力容量结论、未改预生产;未做共享PG/Redis停机、进程崩溃、恢复演练或公网IPv6可达性验收。MinIO仅健康/服务核验,本次无对象存储功能变更。测试机默认代理DNS路径未治理,本次公网回调在已批准临时映射下完成,不能宣称环境DNS已修复。

本机 %TEMP%/cmpp-http-full-20260914/ 保存 results.json、cases.md、baseline.json、final-audit.json、final-reconcile.json、hosts-applied/renewed/restored.json、capacity-before/after/final.json、网络探针和完整测试日志;无凭据/密钥。发布证据 .local-data/releases/20260914T072054-97d133442350-766123b9/ 及前次92目录。需求/设计/测试同步见 整改方案测试用例进度部署手册

请求与断言索引(保留原结果)

# 记录名 结果/分类 HTTP或状态
1 valid_empty_uplinks 通过 200
2 unknown_message 通过 404
3 empty_object 通过 400
4 missing_mobile 通过 400
5 mobile_number 通过 400
6 mobile_null 通过 400
7 mobile_array 通过 400
8 mobile_short 通过 400
9 mobile_long 通过 400
10 mobile_letters 通过 400
11 mobile_prefix 通过 400
12 mobile_space 通过 400
13 missing_content 通过 400
14 content_null 通过 400
15 content_number 通过 400
16 content_object 通过 400
17 content_array 通过 400
18 content_empty 通过 400
19 content_blank 通过 400
20 client_id_number 通过 400
21 client_id_object 通过 400
22 client_id_too_long 通过 400
23 body_array 通过 400
24 body_string 通过 400
25 limit_zero 通过 400
26 limit_negative 通过 400
27 limit_fraction 通过 400
28 limit_nan 通过 400
29 limit_infinite 通过 400
30 limit_over_safe 通过 400
31 limit_duplicate 通过 400
32 date_invalid 通过 400
33 date_end_invalid 通过 400
34 date_reverse 通过 400
35 range_over_limit 通过 400
36 date_duplicate 通过 400
37 keyword_duplicate 通过 400
38 mobile_duplicate 通过 400
39 access_duplicate 通过 400
40 cursor_invalid 通过 400
41 cursor_wrong_json 通过 400
42 cursor_bad_time 通过 400
43 cursor_bad_id 通过 400
44 cursor_extra 通过 400
45 cursor_too_long 通过 400
46 limit_max_clipped 通过 200
47 unknown_uplink 通过 404
48 idem_short 通过 400
49 idem_long 通过 400
50 idem_illegal_ascii 通过 400
51 idem_missing 通过 400
52 malformed_json 通过 400
53 unknown_internal_channel_field expectation_mismatch_unknown_fields_not_forbidden_by_current_contract 403
54 send_permission_disabled 通过 403
55 auth_headers_missing 通过 401
56 nonce_short 通过 401
57 nonce_illegal 通过 401
58 timestamp_expired 通过 401
59 timestamp_non_numeric 通过 401
60 signature_wrong 通过 401
61 unknown_credential 通过 401
62 nonce_first_use 通过 200
63 nonce_replay 通过 401
64 message_query_disabled 通过 403
65 uplink_query_disabled 通过 403
66 ip_not_allowed 通过 403
67 http_api_disabled 通过 403
68 qps_limit_concurrent_queries 通过 200,429,429,429
69 normal_mobile fixture_incomplete_no_approved_template 422
70 normal_mobile_ready 通过 202
71 accepted_idempotent_replay 通过 202
72 accepted_snapshot_immutable_after_delivery 通过 断言
73 idempotency_body_conflict 通过 409
74 expired_credential fixture_clock_skew_server_4s_behind 200
75 revoked_credential 通过 401
76 normal_unicom 通过 202
77 normal_telecom 通过 202
78 long_message 通过 202
79 utf8_escape_newline 通过 202
80 expired_after_server_deadline 通过 401
81 normal_mobile_ready_final 通过 delivered
82 normal_unicom_final 通过 delivered
83 normal_telecom_final 通过 delivered
84 long_message_final 通过 delivered
85 utf8_escape_newline_final 通过 delivered
86 query_by_client_message_id 通过 200
87 isolated_mo 通过 202
88 uplink_page1 通过 200
89 uplink_page2 通过 200
90 uplink_pagination_no_duplicate 通过 断言
91 uplink_detail 通过 200
92 uplink_public_fields 通过 断言
93 uplink_filter_mobile 通过 200
94 uplink_filter_mobile_rows 通过 断言
95 uplink_filter_mobile_empty 通过 200
96 uplink_filter_mobile_empty_rows 通过 断言
97 uplink_filter_access 通过 200
98 uplink_filter_access_rows 通过 断言
99 uplink_filter_keyword 通过 200
100 uplink_filter_keyword_rows 通过 断言
101 uplink_filter_combined 通过 200
102 uplink_filter_combined_rows 通过 断言
103 ambiguous_hidden_cmu0tpvfy001atllegn688fn6 通过 404
104 ambiguous_hidden_cmu0tpvgi001ltlleaz7vda8y 通过 404
105 ambiguous_hidden_cmu0tpvgr001wtlletjox30bi 通过 404
106 concurrent_same_idempotency 通过 断言
107 concurrent_single_supplier_submit 通过 200
108 same_app_message_hidden 通过 404
109 same_app_clientId_hidden 通过 404
110 same_app_uplink_hidden 通过 404
111 same_app_uplink_list 通过 200
112 same_app_list_empty 通过 断言
113 cross_app_message_hidden 通过 404
114 cross_app_clientId_hidden 通过 404
115 cross_app_uplink_hidden 通过 404
116 cross_app_uplink_list 通过 200
117 cross_app_list_empty 通过 断言
118 webhook_url_plain_http 通过 400
119 webhook_url_loopback 通过 400
120 webhook_url_private 通过 400
121 webhook_url_metadata 通过 400
122 webhook_url_ipv6_loopback confirmed_bug_fixed_and_live_retested 500
123 webhook_url_userinfo 通过 400
124 webhook_url_bad_protocol 通过 400
125 webhook_url_invalid_url 通过 400
126 raw_json_whitespace_omitted_clientId 通过 202
127 raw_pretty_final 通过 断言
128 uppercase_prefixed_signature 通过 200
129 raw_body_tamper 通过 401
130 nonce_min_length 通过 200
131 nonce_max_length 通过 200
132 duplicate_clientId_different_idempotency 通过 409
133 duplicate_clientId_no_wire 通过 断言
134 nonexistent_calendar_date confirmed_bug_fixed_and_live_retested 200
135 unknown_method 通过 404
136 post_deploy_authenticated_query 通过 200
137 negative_receipt 通过 202
138 negative_receipt_final 通过 断言
139 negative_receipt_two_simulated_routes 通过 断言
140 negative_receipt_refund 通过 断言
141 initial_webhooks_real_https_hmac 通过 断言
142 hook_ok 通过 202
143 hook_ok_sms_final 通过 断言
144 hook_204 通过 202
145 hook_204_sms_final 通过 断言
146 hook_400 通过 202
147 hook_400_sms_final 通过 断言
148 hook_302 通过 202
149 hook_302_sms_final 通过 断言
150 hook_503 通过 202
151 hook_503_sms_final 通过 断言
152 hook_429 通过 202
153 hook_429_sms_final 通过 断言
154 hook_timeout 通过 202
155 hook_timeout_sms_final 通过 断言
156 hook_max_attempts 通过 202
157 hook_max_attempts_sms_final 通过 断言
158 webhook_ok_terminal 通过 delivered
159 webhook_204_terminal 通过 delivered
160 webhook_400_terminal 通过 failed
161 webhook_302_terminal 通过 failed
162 webhook_503_terminal 通过 delivered
163 webhook_503_retry_consistency 通过 断言
164 webhook_429_terminal 通过 delivered
165 webhook_429_retry_consistency 通过 断言
166 webhook_timeout_terminal 通过 delivered
167 webhook_timeout_retry_consistency 通过 断言
168 webhook_always503_terminal 通过 failed
169 webhook_always503_retry_consistency 通过 断言
170 webhook_manual_retry_success 通过 delivered
171 webhook_delivered_retry_rejected 通过 409
172 webhook_cross_app_retry_hidden 通过 404
173 hook_408 通过 202
174 hook_408_sms_final 通过 断言
175 hook_500 通过 202
176 hook_500_sms_final 通过 断言
177 webhook_408_retry 通过 delivered
178 webhook_500_retry 通过 delivered
179 pre_final_release_reconcile 通过 断言
180 fixed_date_feb30 通过 400
181 fixed_date_code_feb30 通过 断言
182 fixed_date_nonleap 通过 400
183 fixed_date_code_nonleap 通过 断言
184 fixed_date_apr31 通过 400
185 fixed_date_code_apr31 通过 断言
186 fixed_date_hour24 通过 400
187 fixed_date_code_hour24 通过 断言
188 fixed_date_no_timezone 通过 400
189 fixed_date_code_no_timezone 通过 断言
190 fixed_date_empty 通过 400
191 fixed_date_code_empty 通过 断言
192 fixed_date_valid_leap 通过 200
193 fixed_date_valid_date_only 通过 200
194 fixed_date_valid_precision 通过 200
195 fixed_date_valid_offset 通过 200
196 fixed_webhook_ipv6_loopback 通过 400
197 fixed_webhook_ipv6_ula 通过 400
198 fixed_webhook_ipv6_linklocal 通过 400
199 fixed_webhook_ipv6_mapped 通过 400
200 fixed_webhook_ipv6_mapped_private 通过 400
201 invalid_webhook_preserves_endpoint 通过 断言
202 fixed_parser_malformed 通过 400
203 fixed_problem_contract_malformed 通过 断言
204 fixed_parser_primitive 通过 400
205 fixed_problem_contract_primitive 通过 断言
206 fixed_parser_oversize 通过 413
207 fixed_problem_contract_oversize 通过 断言
208 fixed_parser_charset 通过 415
209 fixed_problem_contract_charset 通过 断言
210 fixed_parser_encoding 通过 415
211 fixed_problem_contract_encoding 通过 断言
212 webhook_secret_rotated 通过 断言
213 postdeploy_rotated_secret 通过 202
214 postdeploy_sms_final 通过 delivered
215 fixed_cursor_calendar 通过 400
216 fixed_cursor_code 通过 断言
217 postdeploy_rotated_webhook test_assertion_used_event_messageId_instead_of_event_data_messageId delivered
218 postdeploy_uplink_list 通过 200
219 postdeploy_new_mo_received 通过 断言
220 postdeploy_uplink_detail 通过 200
221 postdeploy_uplink_no_internal_fields 通过 断言
222 postdeploy_isolation_same 通过 404
223 postdeploy_isolation_cross 通过 404
224 postdeploy_rotated_webhook_corrected 通过 delivered
225 postdeploy_uplink_webhook 通过 断言
226 browser_client_docs 通过 断言
227 public_openapi_error_schema 通过 断言
228 public_md_updated 通过 200
229 final_money_reconcile 通过 断言
230 final_submit_reconcile 通过 断言
231 application_drained_before_cleanup 通过 断言
232 test_app_cleanup 通过 断言
233 hosts_exact_restoration 通过 断言
234 final_queues_drained 通过 断言
235 minio_actual_service_health 通过 断言