修复(#4): 抽奖 8 宫格默认值 + 100500 msg 脱敏
Closes part of HIF-4 (Stage 2 前端集成反馈 F8 + F10) F8 (P1): Activity.GridSize 默认值 9 → 8 - migration 02160 ALTER DEFAULT 8(幂等) - 02156 up.sql 注释同步更新 - admin/lottery.go 兜底值 9 → 8 - 布局 A:3x3 挖中心,中心是抽奖按钮不是奖品 F10 (P1): wrapInternal msg 脱敏 - 内部错误 err.Error() 只写日志,不外传 - 用户端 msg 只带通用文案 (xerr.MapErrMsg lookup) - 回归护栏:TestWrapInternal_ScrubsErrorDetailsFromMsg + TestWrapInternal_PreservesCodeErrors CI 全绿;无 API 契约变更;F9 独立在 PR #45 处理
This commit is contained in:
@@ -514,6 +514,52 @@ func errAsCode(err error) (uint32, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// TestWrapInternal_ScrubsErrorDetailsFromMsg is the HIF-4 F10 regression guard.
|
||||
//
|
||||
// wrapInternal 之前把 err.Error() 直接塞 xerr.CodeError.Msg,导致对外响应
|
||||
// {"code":100500,"msg":"insert lottery_claim for draw 15: Error 3140 ..."}
|
||||
// 把 DB 结构/表名/内部包路径全部泄露给 app 端。F10 修法:细节走日志,msg 只
|
||||
// 带通用文案。此测试锁死:"wrapInternal(任意非 CodeError 的原生错误) 返回的
|
||||
// CodeError.Msg 不能等于原始 err.Error()"。
|
||||
func TestWrapInternal_ScrubsErrorDetailsFromMsg(t *testing.T) {
|
||||
sensitive := errors.New("insert lottery_claim for draw 15: Error 3140 (22032): Invalid JSON text: The document is empty")
|
||||
wrapped := wrapInternal(sensitive)
|
||||
if wrapped == nil {
|
||||
t.Fatal("wrapInternal returned nil for non-nil error")
|
||||
}
|
||||
var ce *xerr.CodeError
|
||||
if !errors.As(wrapped, &ce) {
|
||||
t.Fatalf("expected *xerr.CodeError, got %T", wrapped)
|
||||
}
|
||||
if ce.GetErrCode() != xerr.LotteryInternalError {
|
||||
t.Fatalf("expected code=%d, got %d", xerr.LotteryInternalError, ce.GetErrCode())
|
||||
}
|
||||
if strings.Contains(ce.GetErrMsg(), "lottery_claim") ||
|
||||
strings.Contains(ce.GetErrMsg(), "3140") ||
|
||||
strings.Contains(ce.GetErrMsg(), "Invalid JSON") {
|
||||
t.Fatalf("F10 regression: internal error detail leaked to msg: %q", ce.GetErrMsg())
|
||||
}
|
||||
// 反过来断言:msg 应该是标准文案(xerr.MapErrMsg 查表得到)
|
||||
if ce.GetErrMsg() != xerr.MapErrMsg(xerr.LotteryInternalError) {
|
||||
t.Fatalf("expected generic msg %q, got %q", xerr.MapErrMsg(xerr.LotteryInternalError), ce.GetErrMsg())
|
||||
}
|
||||
}
|
||||
|
||||
// TestWrapInternal_PreservesCodeErrors 副断言:已经是 xerr.CodeError 的错误
|
||||
// 不能被 wrap 掉(它们的 msg 是设计过的对外文案,比如 4001/4002/4009)。
|
||||
func TestWrapInternal_PreservesCodeErrors(t *testing.T) {
|
||||
coded := xerr.NewErrCode(xerr.LotteryNoChances)
|
||||
wrapped := wrapInternal(coded)
|
||||
var ce *xerr.CodeError
|
||||
if !errors.As(wrapped, &ce) {
|
||||
t.Fatalf("expected *xerr.CodeError, got %T", wrapped)
|
||||
}
|
||||
if ce.GetErrCode() != xerr.LotteryNoChances {
|
||||
t.Fatalf("F10 side-effect: coded error was rewrapped; got code %d instead of %d",
|
||||
ce.GetErrCode(), xerr.LotteryNoChances)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInsertSnapshots_UnmetReasonsIsValidJSON is the F4 regression guard
|
||||
// (kept from PR E — must survive Stage 2 rebase).
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user