package lua import ( "testing" ) // ★ 上游错误体抽取必须覆盖**网关与腾讯系**形状 —— 这两类实测都漏过。 // // 发现路径(2026-10-16 读生产日志): // // unhandled upstream error body (adapter "openai" lacks transform_error): // status=404 body={"error_msg":"404 Route Not Found"} // unhandled upstream error body (adapter "buddy" lacks transform_error): // status=400 body={"code":11101,"msg":"Non-stream chat request is not supported"} // // 两者都是 `{"error_msg":...}` / `{"msg":...}` —— 旧实现只认 // error/message/detail,于是返回 nil ⇒ 客户端只看到 // `api error 404: unknown error`,而 body 里明明有可用文案。 // // ⚠️ 那两个适配器**都定义了** transform_error(openai.lua:139、buddy.lua:166), // 内核却报 "lacks" ⇒ 一句会把排查带偏的诊断(已另行修正内核文案)。 func TestPreludeErrorExtractionCoversGatewayShapes(t *testing.T) { vm := NewVM(t.TempDir()) defer vm.Stop() // 用**仓库里的真实** openai 适配器(含共享前导),不是自造桩: // 判据必须走真实实现,否则"补了形状"这件事根本没被验。 src, err := bundledAdapters.ReadFile("adapters/openai.lua") if err != nil { t.Fatal(err) } if err := vm.LoadAdapterSource("openai", string(src)); err != nil { t.Fatalf("装载 openai 适配器: %v", err) } cases := []struct { name, body, want string }{ {"APISIX error_msg", `{"error_msg":"404 Route Not Found"}`, "404 Route Not Found"}, {"腾讯 msg(buddy 11101)", `{"code":11101,"msg":"Non-stream chat request is currently not supported"}`, "11101: Non-stream chat request is currently not supported"}, {"error_code + error_msg", `{"error_code":"APIG.0301","error_msg":"verify ak sk signature fail"}`, "APIG.0301: verify ak sk signature fail"}, {"腾讯 displayMsg 本地化(最可读,排最前)", `{"code":11101,"msg":"Non-stream chat request is currently not supported",` + `"displayMsg":{"en":"Invalid request. Please retry","zh":"请求内容不合法,请重试"}}`, "请求内容不合法,请重试 11101: Non-stream chat request is currently not supported"}, {"仅 displayMsg", `{"displayMsg":{"zh":"只有本地化文案"}}`, "只有本地化文案"}, {"仅 error_code", `{"error_code":"InferHub.4291.200"}`, "InferHub.4291.200"}, // 回归:原有的 6 种形状不得丢 {"OpenAI 信封", `{"error":{"message":"boom"}}`, "boom"}, {"裸字符串 error", `{"error":"boom"}`, "boom"}, {"扁平 code/message", `{"message":"boom"}`, "boom"}, {"FastAPI detail", `{"detail":"boom"}`, "boom"}, {"HTML title", `413 Too Large`, "413 Too Large"}, {"裸文本", "plain failure line\nmore", "plain failure line"}, // 腾讯 codebuddy 三层嵌套(实测 429 code=14018 额度用尽): // 此前落到"不识别",客户端只看到通用文案,用户分不清是额度还是故障。 {"buddy 额度用尽嵌套", `{"error":{"data":{"code":14018,"msg":"额度已用尽,请购买加量包","requestId":"r1"}}}`, "14018: 额度已用尽,请购买加量包"}, // 同一形状但没有 code:只回 msg {"buddy 嵌套无码", `{"error":{"data":{"msg":"请求被拒绝"}}}`, "请求被拒绝"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got, ok, err := vm.TransformError("openai", 400, c.body) if err != nil { t.Fatalf("TransformError: %v", err) } if !ok { t.Fatalf("★ 抽不出消息(返回 ok=false)⇒ 客户端只会看到 "+ "'unknown error',而 body 里明明有:%s", c.body) } if got != c.want { t.Fatalf("抽出的文案不对\n 实得: %q\n 期望: %q", got, c.want) } }) } } // 配对判据:真正**没有** transform_error 的适配器仍应返回 ok=false // (不能为了让上面那条通过就把"抽不出"和"没这个钩子"混为一谈)。 func TestPreludeErrorExtractionStillReportsUnknownShapes(t *testing.T) { vm := NewVM(t.TempDir()) defer vm.Stop() src, err := bundledAdapters.ReadFile("adapters/openai.lua") if err != nil { t.Fatal(err) } if err := vm.LoadAdapterSource("openai", string(src)); err != nil { t.Fatalf("装载 openai 适配器: %v", err) } // 一个确实认不出的 JSON(没有 message/error/detail/msg/error_msg) got, ok, err := vm.TransformError("openai", 400, `{"weird":true}`) if err != nil { t.Fatalf("TransformError: %v", err) } if ok { t.Fatalf("认不出的形状不该硬凑文案,实得 %q", got) } }