fix(httpclient): 信封 message 默认不进错误,渠道上游自由文本一并收口

信封路径(非 2xx + 信封体、2xx + code!=0)此前把上游 message 经 RedactText
拼进 error,而上游会在 message 里回显被拒的请求正文 —— 她的原话因此进了错误
与调用方日志(M49 探针实测:code 1: invalid content: 她刚才说的那句话 ...)。

- envelope message 改为**默认不带**(新 opt-in WithEnvelopeMessage(),与
  trace.UpstreamError 的 message 同一口径);code 与上游 status 保留,
  errors.Is 的 ErrAPI / trace.ErrUpstream 链不变。
- opt-in 下统一走单一清洗 upstreamText:压平空白 + trace.RedactTextLimited,
  上限 40 rune(比 trace 的 128 短:错误里那句话是提示,不是摘要)。
- plugins/channels 同族出口一并收口(扫过全部,命中 4 处):
  telegram description、qq_obv11 msg(错误 + logEchoFailure 日志)、
  weixin_ilink CDN x-error-message;qq 的 status 字段改走 trace.ErrorToken
  令牌守卫。被剥掉的一律由 status / error_code / retcode 承担分类。

每条新增/改写用例都做过变异验证(把字段拼回去即变红)。
This commit is contained in:
AskaEth committed 2026-10-26 19:28:15 +08:00
1 parent 3a0cc64160
commit 996201c4ed
11 files changed
+432 -127

No files matched your search

@@ -264,14 +264,16 @@ func TestOutboundMedia_UploadWithoutParamHeaderIsHonest(t *testing.T) {
}
// TestOutboundMedia_UploadClientErrorIsNotRetried 覆盖 ④-b:4xx 是客户端错误,重试徒劳
// (官方 cdn-upload.ts:47-52 立即抛出),且服务端的 `x-error-message` 必须进错误信息 ——
// 失败要可诊断。
// (官方 cdn-upload.ts:47-52 立即抛出)。且服务端的 `x-error-message` **不进错误**
// (M50 口径:它是上游自由文本面,与信封 message / Bot API description / OneBot msg
// 同族;4xx 这个 status 已足够分类)—— 变异:把它拼回错误 ⇒ 本用例后半段立刻红。
func TestOutboundMedia_UploadClientErrorIsNotRetried(t *testing.T) {
t.Parallel()
const sentinel = "invalid content: 她刚才说的那句话"
s := newUploadCaptureServer(t, handlerResult{
status: http.StatusBadRequest,
body: []byte("ignored"),
headers: map[string]string{"x-error-message": "bad filekey"},
headers: map[string]string{"x-error-message": sentinel},
})
ad := newConnectedAdapter(t, Config{BaseURL: s.srv.URL, CDNBaseURL: s.srv.URL, BotToken: "tok"}, s.srv)
ad.recordInbound("tok-1", adapter.UnifiedMessage{SenderID: "u-1", Timestamp: ad.clock()})
@@ -285,8 +287,11 @@ func TestOutboundMedia_UploadClientErrorIsNotRetried(t *testing.T) {
if err == nil {
t.Fatal("4xx 上传失败却报成功")
}
if !strings.Contains(err.Error(), "bad filekey") {
t.Fatalf("错误里没有服务端说明:%v", err)
if strings.Contains(err.Error(), "她刚才说的那句话") {
t.Fatalf("服务端的 x-error-message 进了错误:%v", err)
}
if !strings.Contains(err.Error(), "status 400") {
t.Fatalf("剥掉服务端说明时把 status 一起丢了:%v", err)
}
if s.attempts != 1 {
t.Fatalf("4xx 被重试了:上传尝试 %d 次, want 1", s.attempts)