docs(comments): 注释中文化 · plugins/channels/weixin_ilink(20 文件,纯替换)
- 只翻注释/文档字符串:剥掉行尾注释后,每条「-」都有逐字节相同的「+」对应行(8 组,代码零改动) - 保留协议/线上标识符原文:errcode、bot_token、context_token、getupdates、binded_redirect、PKCS#7、ECB 等 - 术语对齐既有译法:owner→主人、peer→对端、long poll→长轮询、backoff→退避、sentinel error→哨兵错误 - 已知既有欠债(未顺手修):login_reuse_test.go 在 HEAD 上就未 gofmt 干净;login_test.go 的 TestStartLogin_ScanStatusTransition 91 行 >80(HEAD 同为 91 行)
This commit is contained in:
1 parent
09fba77f87
commit
95f97f5df3
20 files changed
+488
-590
No files matched your search
@@ -11,13 +11,12 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||||
)
|
||||
|
||||
// These tests cover the explicit session-rejection state machine (session_state.go):
|
||||
// iLink answering errcode=-14 must be visible as "re-scan required" instead of a
|
||||
// silently mute adapter or a connect that reports success with a credential iLink
|
||||
// already refused.
|
||||
// 这些测试覆盖显式的「会话被拒绝」状态机(session_state.go):iLink 回复
|
||||
// errcode=-14 必须表现为「需要重新扫码」,而不是悄悄变哑的适配器、或一次拿着已被
|
||||
// iLink 拒绝的凭据却上报成功的连接。
|
||||
|
||||
// rejectingServer answers every getupdates long poll with the rejected-session
|
||||
// errcode, the way iLink refuses a session credential.
|
||||
// rejectingServer 对每次 getupdates 长轮询都回以「会话被拒绝」错误码,正如 iLink
|
||||
// 拒绝一份会话凭据那样。
|
||||
func rejectingServer(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
return newJSONServer(t, nil, func(req recordedReq) handlerResult {
|
||||
@@ -28,9 +27,8 @@ func rejectingServer(t *testing.T) *httptest.Server {
|
||||
})
|
||||
}
|
||||
|
||||
// rejectByPoll drives the adapter into the rejected state through the long poll:
|
||||
// ReadMessages must return on errcode=-14 and the adapter must report the
|
||||
// rescan-required state.
|
||||
// rejectByPoll 通过长轮询把适配器驱入被拒绝状态:ReadMessages 必须在 errcode=-14 时
|
||||
// 返回,且适配器必须报告「需要重新扫码」状态。
|
||||
func rejectByPoll(t *testing.T, ad *Adapter) {
|
||||
t.Helper()
|
||||
done := make(chan struct{})
|
||||
@@ -44,8 +42,7 @@ func rejectByPoll(t *testing.T, ad *Adapter) {
|
||||
}
|
||||
}
|
||||
|
||||
// newRejectedByPollAdapter builds a connected adapter and drives it into the
|
||||
// rejected state via the poll path.
|
||||
// newRejectedByPollAdapter 构造一个已连接的适配器,并经轮询路径把它驱入被拒绝状态。
|
||||
func newRejectedByPollAdapter(t *testing.T, srv *httptest.Server) *Adapter {
|
||||
t.Helper()
|
||||
ad := newConnectedAdapter(t, Config{BaseURL: srv.URL, PollInterval: time.Millisecond, BotToken: "tok-old"}, srv)
|
||||
@@ -53,10 +50,9 @@ func newRejectedByPollAdapter(t *testing.T, srv *httptest.Server) *Adapter {
|
||||
return ad
|
||||
}
|
||||
|
||||
// assertRejectedSession asserts the observable contract of a session iLink rejected:
|
||||
// not connected, rescan required with a reason, HealthCheck distinguishing the
|
||||
// rejected state from not-connected, and Connect refusing with ErrSessionRejected
|
||||
// instead of a fake OK.
|
||||
// assertRejectedSession 断言「会话被 iLink 拒绝」的可观测契约:未连接、需要重新扫码
|
||||
// 且有原因、HealthCheck 能把被拒绝状态与未连接区分开、以及 Connect 以
|
||||
// ErrSessionRejected 拒绝而不是假 OK。
|
||||
func assertRejectedSession(t *testing.T, ad *Adapter) {
|
||||
t.Helper()
|
||||
if ad.IsConnected() {
|
||||
@@ -120,7 +116,7 @@ func TestSessionRejected_SendMarksRescan(t *testing.T) {
|
||||
if ad.IsConnected() {
|
||||
t.Fatal("IsConnected() = true after a send saw errcode=-14")
|
||||
}
|
||||
// A later send reports the rejected session itself, not a bare "not connected".
|
||||
// 之后的发送上报的是会话被拒绝本身,而不是光秃秃的「未连接」。
|
||||
err = ad.SendMessage(context.Background(), adapter.UnifiedResponse{
|
||||
Messages: []adapter.UnifiedMessage{{SenderID: "u-1", Content: "again"}},
|
||||
})
|
||||
@@ -156,8 +152,8 @@ func TestSessionRejected_ClearedByScanLogin(t *testing.T) {
|
||||
ad := newRejectedByPollAdapter(t, srv)
|
||||
assertRejectedSession(t, ad)
|
||||
|
||||
// A rejected token must not short-circuit the scan flow: it is not a usable
|
||||
// bind, and ErrAlreadyLoggedIn would leave the owner no way back.
|
||||
// 被拒绝的 token 不得短路扫码流程:它不是可用绑定,而 ErrAlreadyLoggedIn 会让
|
||||
// 主人无路可退。
|
||||
qr, err := ad.StartLogin(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("StartLogin() on a rejected session = %v, want a fresh QR", err)
|
||||
@@ -166,7 +162,7 @@ func TestSessionRejected_ClearedByScanLogin(t *testing.T) {
|
||||
t.Fatalf("StartLogin() qrcode = %q, want qr-re", qr.Qrcode)
|
||||
}
|
||||
|
||||
// The confirmed scan delivers a new token: the rejected state must clear.
|
||||
// 确认的扫码交付了新 token:被拒绝状态必须被清除。
|
||||
waitFor(t, func() bool { ok, _ := ad.RescanRequired(); return !ok })
|
||||
if err := ad.Connect(context.Background()); err != nil {
|
||||
t.Fatalf("Connect() after re-scan = %v, want nil", err)
|
||||
@@ -184,8 +180,8 @@ func TestSessionRejected_ClearedByScanLogin(t *testing.T) {
|
||||
t.Fatalf("LoginStatus() = %q, want confirmed after a successful re-scan", status)
|
||||
}
|
||||
|
||||
// The re-scan asked for a real rebind: the rejected token is not advertised as a
|
||||
// local token (that is what invites binded_redirect with no new credentials).
|
||||
// 本次重扫请求的是真正的重新绑定:被拒绝的 token 不作为本地 token 上报(正是
|
||||
// 上报它才会招来不带新凭据的 binded_redirect)。
|
||||
reqs := rec.find(func(q recordedReq) bool { return q.path == "/ilink/bot/get_bot_qrcode" })
|
||||
if len(reqs) != 1 {
|
||||
t.Fatalf("get_bot_qrcode requests = %d, want 1", len(reqs))
|
||||
@@ -202,7 +198,7 @@ func TestSessionRejected_ClearedByNewToken(t *testing.T) {
|
||||
srv := rejectingServer(t)
|
||||
ad := newRejectedByPollAdapter(t, srv)
|
||||
|
||||
// The rejected token is still configured: the session stays rejected.
|
||||
// 被拒绝的 token 仍在配置中:会话保持被拒绝。
|
||||
if err := ad.Connect(context.Background()); !errors.Is(err, ErrSessionRejected) {
|
||||
t.Fatalf("Connect() = %v, want ErrSessionRejected while the rejected token is unchanged", err)
|
||||
}
|
||||
@@ -245,8 +241,7 @@ func TestSessionRejected_LoginStatusReportsRescanAndAllowsRescan(t *testing.T) {
|
||||
}, srv)
|
||||
rejectByPoll(t, ad)
|
||||
|
||||
// A rejected credential must not read as "confirmed": the panel would re-persist
|
||||
// it and call the bind a success.
|
||||
// 被拒绝的凭据不得被读作 "confirmed":否则面板会重新持久化它并把绑定当作成功。
|
||||
status, detail := ad.LoginStatus(context.Background())
|
||||
if status != LoginStatusFailed {
|
||||
t.Fatalf("LoginStatus() = %q, want failed while iLink rejects the stored token", status)
|
||||
@@ -255,8 +250,7 @@ func TestSessionRejected_LoginStatusReportsRescanAndAllowsRescan(t *testing.T) {
|
||||
t.Fatal("LoginStatus() detail is empty, want the re-scan reason")
|
||||
}
|
||||
|
||||
// The panel's 扫码登录 must still mint a QR (ErrAlreadyLoggedIn would block the
|
||||
// only recovery path).
|
||||
// 面板的扫码登录仍必须能生成二维码(ErrAlreadyLoggedIn 会堵死唯一的恢复路径)。
|
||||
qr, err := ad.StartLogin(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("StartLogin() = %v, want a fresh QR", err)
|
||||
@@ -264,16 +258,15 @@ func TestSessionRejected_LoginStatusReportsRescanAndAllowsRescan(t *testing.T) {
|
||||
if qr.Qrcode != "qr-2" {
|
||||
t.Fatalf("StartLogin() qrcode = %q, want qr-2", qr.Qrcode)
|
||||
}
|
||||
// While the re-scan is in progress its own state is reported, not the rejected one.
|
||||
// 重扫进行期间上报的是它自己的状态,而不是被拒绝状态。
|
||||
if status, _ := ad.LoginStatus(context.Background()); status != LoginStatusWaiting {
|
||||
t.Fatalf("LoginStatus() during the re-scan = %q, want waiting", status)
|
||||
}
|
||||
}
|
||||
|
||||
// setBotToken swaps the configured bot token, standing in for a panel save +
|
||||
// hot reload (which rebuilds the adapter) or any other in-place config refresh;
|
||||
// the adapter has no public setter because production always constructs a fresh
|
||||
// instance from the new config.
|
||||
// setBotToken 替换已配置的 bot token,用来模拟面板保存 + 热重载(会重建适配器)
|
||||
// 或任何其他原地配置刷新;适配器没有公开的 setter,因为生产环境总是依据新配置构造
|
||||
// 全新实例。
|
||||
func setBotToken(ad *Adapter, token string) {
|
||||
ad.mu.Lock()
|
||||
ad.cfg.BotToken = token
|
||||
|
||||
Reference in new issue
Block a user