mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
两个真实缺陷,都属"看着能用、一用就错"的假阳性 —— 正是用户报「完全不可用」时
最容易误导人的那类信号。
## ① account_test 一个网络请求都不发
原实现只做 syncAccountSource,然后查内核源列表里有没有那条记录,就返回
`{ok=true, ms=1}`(`ms` 甚至是硬编码的 1)。于是"测试账号"**永远绿**,
包括凭据已失效、被限流、上游宕机的情况。
照参考 account-probe 的语义改为**真打一次上游**:优先余额端点(最轻、所有有
余额的平台都有),退化到 checkin_status;两者都没有的平台如实报
「未声明可探测端点,仅校验本地凭据与源」,**不假装成功**。
失败还分两类(照参考:只说"失败"用户不知道该做什么):
- `kind=auth`(401/403/invalid_grant/expired)→ "凭据已失效,请重新登录该账号"
- `kind=transient` → "上游暂时不可用,可稍后重试"
## ② fetchAccountJSON 只看 HTTP 状态码,业务级错误被当成功
多家上游**HTTP 200 但 body 里带 error**(实测 buddy 的 invalid_grant 就是这个
形态)。只看状态码时"凭据已失效"会被当成成功。现补判 body 里的
`error`(字符串/对象)、`code`(非 0 即失败)。
这一条同时修好了所有走这个助手的路径:余额查询、签到状态、以及新的账号探测。
## 判据
`channels_account_probe_test.go`(假上游 + 请求计数器)三条:
1. 正常凭据:**必须真的发了请求**(用原子计数器证明 hits>0)且 probed=true
2. 凭据失效:ok=false、kind=auth、必须带可操作提示
3. 无凭据账号:如实报错,不假装成功
回归:9 个包全绿。
106 lines
3.7 KiB
Go
106 lines
3.7 KiB
Go
package core
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"sync/atomic"
|
||
"testing"
|
||
)
|
||
|
||
// `account_test` 必须**真打一次上游**。
|
||
//
|
||
// 真实缺陷:此前它一个网络请求都不发 —— 只 syncAccountSource 后查内核源列表里
|
||
// 有没有那条记录,就返回 {ok:true, ms:1}。于是"测试账号"永远绿,包括凭据已失效、
|
||
// 被限流、上游宕机的情况。这正是「看着能用、一用就错」的假阳性,也正是用户报
|
||
// 「完全不可用」时最容易误导人的那种信号。
|
||
//
|
||
// 判据把三条都钉住:① 真发请求 ② 上游失败时必须 ok=false ③ 失败要区分
|
||
// "凭据失效"(需重新登录)与"暂时性失败"(可重试)—— 只说"失败"用户不知道该做什么。
|
||
func TestAccountTestReallyProbesUpstream(t *testing.T) {
|
||
var hits int32
|
||
fail := false
|
||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
atomic.AddInt32(&hits, 1)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
if fail {
|
||
w.WriteHeader(200)
|
||
_, _ = w.Write([]byte(`{"error":"invalid_grant: token expired"}`))
|
||
return
|
||
}
|
||
body, _ := json.Marshal(map[string]interface{}{
|
||
"data": map[string]interface{}{"Response": map[string]interface{}{
|
||
"Data": map[string]interface{}{"Accounts": []map[string]interface{}{
|
||
{"Status": 1, "CycleCapacityRemain": 100},
|
||
}},
|
||
}},
|
||
})
|
||
_, _ = w.Write(body)
|
||
}))
|
||
defer ts.Close()
|
||
|
||
_, ps := poolCore(t)
|
||
if _, err := ps.Call("channels", "set_endpoints", map[string]interface{}{
|
||
"platform": "buddy",
|
||
"balance": map[string]interface{}{"url": ts.URL},
|
||
}); err != nil {
|
||
t.Fatalf("set_endpoints: %v", err)
|
||
}
|
||
if _, err := ps.Call("channels", "account_add", map[string]interface{}{
|
||
"platform": "buddy", "account_id": "probeacct",
|
||
"credential": `{"access_token":"TOK"}`,
|
||
}); err != nil {
|
||
t.Fatalf("account_add: %v", err)
|
||
}
|
||
|
||
// ① 正常路径:必须真的打了上游
|
||
atomic.StoreInt32(&hits, 0)
|
||
m, err := ps.Call("channels", "account_test", map[string]interface{}{
|
||
"platform": "buddy", "account_id": "probeacct",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("account_test: %v", err)
|
||
}
|
||
w, _ := m.(map[string]interface{})
|
||
j, _ := w["json"].(map[string]interface{})
|
||
if j["ok"] != true {
|
||
t.Fatalf("正常凭据应 ok=true: %#v", j)
|
||
}
|
||
if j["probed"] != true {
|
||
t.Errorf("必须标记 probed=true 并真的发请求;实得 probed=%v", j["probed"])
|
||
}
|
||
if n := atomic.LoadInt32(&hits); n == 0 {
|
||
t.Fatal("account_test 未向上游发任何请求 —— 这就是「看着能用」的假阳性")
|
||
}
|
||
|
||
// ② 凭据失效:必须 ok=false,且归类为 auth(给出可操作提示)
|
||
fail = true
|
||
m2, _ := ps.Call("channels", "account_test", map[string]interface{}{
|
||
"platform": "buddy", "account_id": "probeacct",
|
||
})
|
||
j2, _ := m2.(map[string]interface{})["json"].(map[string]interface{})
|
||
if j2["ok"] != false {
|
||
t.Errorf("凭据失效时必须 ok=false,实得 %v", j2["ok"])
|
||
}
|
||
if j2["kind"] != "auth" {
|
||
t.Errorf("应归类为 auth(需重新登录),实得 %v", j2["kind"])
|
||
}
|
||
if s, _ := j2["action"].(string); s == "" {
|
||
t.Error("失败必须给出可操作提示(否则用户不知道要重新登录还是等一会)")
|
||
}
|
||
|
||
// ③ 无凭据的账号:如实报错,不假装成功
|
||
if _, err := ps.Call("channels", "account_add", map[string]interface{}{
|
||
"platform": "buddy", "account_id": "naked",
|
||
}); err != nil {
|
||
t.Fatalf("account_add(无凭据): %v", err)
|
||
}
|
||
m3, _ := ps.Call("channels", "account_test", map[string]interface{}{
|
||
"platform": "buddy", "account_id": "naked",
|
||
})
|
||
j3, _ := m3.(map[string]interface{})["json"].(map[string]interface{})
|
||
if j3["ok"] != false {
|
||
t.Errorf("无凭据时必须 ok=false,实得 %v", j3["ok"])
|
||
}
|
||
}
|