feat(weixin_ilink): 出站媒体走通(主人已批的三项)
按官方包 @tencent-weixin/openclaw-weixin 2.4.9 源码对齐: ① 上传由 PUT 改 POST;② 上传凭据读响应头 x-encrypted-param; ③ nativeMediaItem 改为官方 *_item.media.encrypt_query_param 嵌套形状(含 aes_key, 图片优先 image_item.aeskey)。 配合前一版已修的两点(入站按条密钥、媒体失败不阻断文本),机器人现在可主动发出文件/图片。 (逐项测试、变异红行与诚实边界见该切片报告)
This commit is contained in:
1 parent
59a1ac4056
commit
f8f2e9c54c
7 files changed
+847
-246
No files matched your search
@@ -23,15 +23,20 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
)
|
||||
|
||||
// mediaSendServer 是假 iLink:getuploadurl 按 upload 作答,上传成功,sendmessage 回 ret:0。
|
||||
// mediaSendServer 是假 iLink:getuploadurl 按 upload 作答,**CDN 上传按官方口径回
|
||||
// 200 + `x-encrypted-param` 头**(POST),sendmessage 回 ret:0。
|
||||
func mediaSendServer(t *testing.T, rec *reqRecorder, upload handlerResult) *httptest.Server {
|
||||
t.Helper()
|
||||
return newJSONServer(t, rec, func(req recordedReq) handlerResult {
|
||||
switch {
|
||||
case req.method == http.MethodPost && req.path == "/ilink/bot/getuploadurl":
|
||||
return upload
|
||||
case req.method == http.MethodPut:
|
||||
return handlerResult{status: http.StatusOK, body: []byte("ok")}
|
||||
case req.method == http.MethodPost && req.path == "/cdn/up":
|
||||
return handlerResult{
|
||||
status: http.StatusOK,
|
||||
body: []byte("ok"),
|
||||
headers: map[string]string{uploadParamHeader: downloadParamFixture},
|
||||
}
|
||||
case req.method == http.MethodPost && req.path == "/ilink/bot/sendmessage":
|
||||
return handlerResult{body: jsonBody(map[string]any{"ret": 0})}
|
||||
default:
|
||||
@@ -40,6 +45,10 @@ func mediaSendServer(t *testing.T, rec *reqRecorder, upload handlerResult) *http
|
||||
})
|
||||
}
|
||||
|
||||
// downloadParamFixture 是假 CDN 回填的下载凭据(真实值由服务端生成,测试只关心它被
|
||||
// 原样带进 item 的 media.encrypt_query_param)。
|
||||
const downloadParamFixture = "dl+param/1="
|
||||
|
||||
// newLoggingSendAdapter 装配一个把日志写进 buf 的已连接适配器,用来断言失败**不静默**。
|
||||
func newLoggingSendAdapter(t *testing.T, srv *httptest.Server, buf *bytes.Buffer) *Adapter {
|
||||
t.Helper()
|
||||
@@ -193,9 +202,9 @@ func TestGetUploadURL_RequestCarriesProtocolFields(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("aeskey 不是十六进制:%v", err)
|
||||
}
|
||||
puts := rec.find(func(q recordedReq) bool { return q.method == http.MethodPut })
|
||||
if len(puts) != 0 {
|
||||
t.Fatalf("上传地址缺失时不该发起上传:%d 次", len(puts))
|
||||
// 上传地址缺失时**一次上传请求都不该发出**(只应有那一次 getuploadurl)。
|
||||
if reqs := rec.find(func(q recordedReq) bool { return q.method == http.MethodPost }); len(reqs) != 1 {
|
||||
t.Fatalf("POST 请求 = %d 次, want 1(只有 getuploadurl;地址缺失就不该上传)", len(reqs))
|
||||
}
|
||||
if len(rawKey) != 16 {
|
||||
t.Fatalf("aeskey = %d 字节, want 16", len(rawKey))
|
||||
@@ -212,8 +221,12 @@ func TestOutboundMedia_UsesUploadFullURL(t *testing.T) {
|
||||
switch {
|
||||
case req.method == http.MethodPost && req.path == "/ilink/bot/getuploadurl":
|
||||
return handlerResult{body: jsonBody(map[string]any{"upload_full_url": srv.URL + "/cdn/up"})}
|
||||
case req.method == http.MethodPut && req.path == "/cdn/up":
|
||||
return handlerResult{status: http.StatusOK, body: []byte("ok")}
|
||||
case req.method == http.MethodPost && req.path == "/cdn/up":
|
||||
return handlerResult{
|
||||
status: http.StatusOK,
|
||||
body: []byte("ok"),
|
||||
headers: map[string]string{uploadParamHeader: downloadParamFixture},
|
||||
}
|
||||
case req.method == http.MethodPost && req.path == "/ilink/bot/sendmessage":
|
||||
return handlerResult{body: jsonBody(map[string]any{"ret": 0})}
|
||||
default:
|
||||
@@ -248,8 +261,12 @@ func TestOutboundMedia_BuildsUploadURLFromUploadParam(t *testing.T) {
|
||||
switch {
|
||||
case req.method == http.MethodPost && req.path == "/ilink/bot/getuploadurl":
|
||||
return handlerResult{body: jsonBody(map[string]any{"upload_param": "ab+cd="})}
|
||||
case req.method == http.MethodPut && req.path == "/upload":
|
||||
return handlerResult{status: http.StatusOK, body: []byte("ok")}
|
||||
case req.method == http.MethodPost && req.path == "/upload":
|
||||
return handlerResult{
|
||||
status: http.StatusOK,
|
||||
body: []byte("ok"),
|
||||
headers: map[string]string{uploadParamHeader: downloadParamFixture},
|
||||
}
|
||||
case req.method == http.MethodPost && req.path == "/ilink/bot/sendmessage":
|
||||
return handlerResult{body: jsonBody(map[string]any{"ret": 0})}
|
||||
default:
|
||||
@@ -268,10 +285,13 @@ func TestOutboundMedia_BuildsUploadURLFromUploadParam(t *testing.T) {
|
||||
t.Fatalf("SendMessage(media, upload_param): %v", err)
|
||||
}
|
||||
|
||||
puts := rec.find(func(q recordedReq) bool { return q.method == http.MethodPut })
|
||||
puts := rec.find(func(q recordedReq) bool { return q.path == "/upload" })
|
||||
if len(puts) != 1 {
|
||||
t.Fatalf("上传请求 = %d 次, want 1", len(puts))
|
||||
}
|
||||
if puts[0].method != http.MethodPost {
|
||||
t.Fatalf("上传方法 = %s, want POST(官方口径)", puts[0].method)
|
||||
}
|
||||
if !strings.Contains(puts[0].query, "encrypted_query_param=ab%2Bcd%3D") {
|
||||
t.Fatalf("上传查询串 = %q, want 转义后的 encrypted_query_param", puts[0].query)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user