07cf121aac
现象(用户反馈): 获取签到表单失败: 获取当日任务列表失败: 解析任务列表响应失败: cpdaily_cookie_expired 期望:能自动续期就续期;续不了就明确引导重新登录(前端据此跳登录页)。 实际:既没提示重新登录,还把内部错误码 cpdaily_cookie_expired 直接显示出来。 根因:自动续期成功、但用新 cookie 重试仍判登录态失效时, 重新拿到的错误(仍是 ErrCookieExpired)被原样返回。 而 needReLoginSignal() 只认 ErrPasswordChanged / ErrNeedReLogin, 不认识 ErrCookieExpired,于是: - errMsg 保持 "前缀 + cpdaily_cookie_expired"; - needReLogin=false,前端不会引导重新登录。 这属于「续期拿到了凭证,但凭证其实无效」的情形(长期凭证过期、 学校侧强制下线等),语义上等同于必须重新登录。 改动: 1. withCookieRefresh:续期后重试仍报登录态失效 → 升级为 ErrNeedReLogin, 这是所有 withCookieRefresh 调用方的统一入口,一处修全覆盖; 2. 签到执行路径:续期后重试仍过期同样升级,并加注释说明; 3. 新增 userFacingError(prefix, err):输出前统一兜底, 任何残留的登录态失效语义都转成「登录凭证已过期,请重新登录」, 普通错误保留原文。即使某条链路漏做升级,也不会再泄露内部错误码; 4. 「获取签到表单」与「签到提交」两处失败文案改用 userFacingError, needReLogin 一并由它给出(前端据此跳登录页)。 测试:原始 ErrCookieExpired、多层 %w 包装、ErrNeedReLogin、 JSON 文本「未登录」均转成引导重登文案且不含内部错误码; 普通错误原样保留;nil 不触发重登。
92 lines
2.7 KiB
Go
92 lines
2.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
cpd "cpdaily-new/internal/service/cpdaily"
|
|
)
|
|
|
|
// userFacingError:保证内部错误码 cpdaily_cookie_expired 不会原样展示给用户。
|
|
//
|
|
// 线上问题:用户拿到 "获取签到表单失败: 获取当日任务列表失败: 解析任务列表响应失败:
|
|
// cpdaily_cookie_expired",既看不懂也没引导重新登录。
|
|
func TestUserFacingErrorHidesInternalCode(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
prefix string
|
|
err error
|
|
wantContains string
|
|
wantReLogin bool
|
|
wantNotSecret string
|
|
}{
|
|
{
|
|
name: "原始 ErrCookieExpired",
|
|
prefix: "获取签到表单失败: ",
|
|
err: cpd.ErrCookieExpired,
|
|
wantContains: "登录凭证已过期,请重新登录",
|
|
wantReLogin: true,
|
|
wantNotSecret: "cpdaily_cookie_expired",
|
|
},
|
|
{
|
|
name: "多层包装后仍能识别",
|
|
prefix: "获取签到表单失败: ",
|
|
err: fmt.Errorf("获取当日任务列表失败: %w", fmt.Errorf("解析任务列表响应失败: %w", cpd.ErrCookieExpired)),
|
|
wantContains: "登录凭证已过期,请重新登录",
|
|
wantReLogin: true,
|
|
wantNotSecret: "cpdaily_cookie_expired",
|
|
},
|
|
{
|
|
name: "ErrNeedReLogin",
|
|
prefix: "签到失败(晚查寝): ",
|
|
err: fmt.Errorf("%w: 续期失败", ErrNeedReLogin),
|
|
wantContains: "登录凭证已过期,请重新登录",
|
|
wantReLogin: true,
|
|
wantNotSecret: "cpdaily_cookie_expired",
|
|
},
|
|
{
|
|
name: "JSON 形式未登录",
|
|
prefix: "获取签到表单失败: ",
|
|
err: errors.New("接口返回: 未登录"),
|
|
wantContains: "登录凭证已过期,请重新登录",
|
|
wantReLogin: true,
|
|
wantNotSecret: "",
|
|
},
|
|
{
|
|
name: "普通错误原样保留",
|
|
prefix: "获取签到表单失败: ",
|
|
err: errors.New("网络超时"),
|
|
wantContains: "网络超时",
|
|
wantReLogin: false,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
msg, needLogin := userFacingError(c.prefix, c.err)
|
|
if needLogin != c.wantReLogin {
|
|
t.Fatalf("needReLogin=%v, want %v", needLogin, c.wantReLogin)
|
|
}
|
|
if !strings.Contains(msg, c.wantContains) {
|
|
t.Fatalf("文案 %q 应包含 %q", msg, c.wantContains)
|
|
}
|
|
if c.wantNotSecret != "" && strings.Contains(msg, c.wantNotSecret) {
|
|
t.Fatalf("文案 %q 不应包含内部错误码 %q", msg, c.wantNotSecret)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// nil 错误不应触发重新登录。
|
|
func TestUserFacingErrorNil(t *testing.T) {
|
|
msg, need := userFacingError("前缀: ", nil)
|
|
if need {
|
|
t.Fatal("nil 不应要求重新登录")
|
|
}
|
|
if msg != "前缀: " {
|
|
t.Fatalf("应原样返回前缀,实际 %q", msg)
|
|
}
|
|
}
|