Files
cpdaily_xiaozhushou/backed/internal/handler/auth_helper_expired_test.go
T
becomer 07cf121aac 修 cookie 自动续期后仍失效时把内部错误码暴露给用户
现象(用户反馈):
  获取签到表单失败: 获取当日任务列表失败: 解析任务列表响应失败: 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 不触发重登。
2026-10-18 16:36:31 +08:00

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)
}
}