Files
qitongxue-api/internal/logic/order_pay_tz_test.go
2026-09-29 10:57:01 +08:00

79 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package logic
import (
"context"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"tool-api/internal/consts"
)
// ============================================================================
// T06 · 优惠域时区错位回归(真库 IT,IT_DB 门控)
//
// 背景:读侧恒定走 gtime.StrToTime(恒 Local,与 DSN 无关),写入走驱动 loc(UTC),
// 于是「Go 侧读回值」比真实早 8h —— 任何 `readback.Before/After(now)` 的过期判定都会错。
// 唯一正确治法:把 expire_at / valid_to 的过期判定挪到 **SQL 侧**(绑定 gtime 参数,
// 与写入同源),绝不在 Go 侧比较读回时间。
//
// 本用例即 T06 验收:
// 断言① expire_at = now+30min 的免单券应**放行**(可占用);
// 断言② expire_at = now-1min 的免单券应**拒绝**(4011)。
//
// 修复前:断言①因读回早 8h 被判「已过期」而拒绝 → 本用例失败(即 T06 的失败证据)。
//
// 运行:$env:IT_DB=1; go test ./internal/logic/ -run TestOrderCouponExpireSqlSide -count=1 -v
// ============================================================================
// insertProbeCoupon 插入一张探测免单券(指定 expire_at),返回券 id。
func insertProbeCoupon(ctx context.Context, userId int64, expireAt *gtime.Time) (int64, error) {
return g.Model(consts.TableUserCoupons).Data(g.Map{
"coupon_id": 0,
"user_id": userId,
"coupon_title": "TZ 探测免单券",
"coupon_type": consts.CouponTypeFreebie,
"kind": consts.CouponKindFreebie,
"status": consts.UserCouponStatusUnused,
"source": consts.UserCouponSourceAdmin,
"expire_at": expireAt,
"created_at": gtime.Now(),
"updated_at": gtime.Now(),
}).InsertAndGetId()
}
// TestOrderCouponExpireSqlSide 券过期判定 SQL 侧归一(T06 验收)。
func TestOrderCouponExpireSqlSide(t *testing.T) {
ctx := requireOrderIT(t)
p := setupOrderProbe(ctx, t)
defer p.cleanup(ctx)
res := createMemberOrder(ctx, t, p)
// 断言①:expire_at = now+30min → 应放行(可占用)
freshId, err := insertProbeCoupon(ctx, p.userId, gtime.Now().Add(30*time.Minute))
if err != nil {
t.Fatalf("insert fresh coupon: %v", err)
}
if _, err = applyInTx(ctx, t, p.userId, res.OutTradeNo, "", freshId); err != nil {
t.Fatalf("断言①失败:expire_at=now+30min 应放行,实得 err=%v (code=%d)", err, codeOf(err))
}
// 清除优惠(释放刚占用的券),不影响下一断言
if _, err = applyInTx(ctx, t, p.userId, res.OutTradeNo, "", 0); err != nil {
t.Fatalf("清除优惠失败: %v", err)
}
// 断言②:expire_at = now-1min → 应拒绝 4011
staleId, err := insertProbeCoupon(ctx, p.userId, gtime.Now().Add(-1*time.Minute))
if err != nil {
t.Fatalf("insert stale coupon: %v", err)
}
if _, err = applyInTx(ctx, t, p.userId, res.OutTradeNo, "", staleId); err == nil {
t.Fatalf("断言②失败:expire_at=now-1min 应被拒绝,实得放行")
} else if codeOf(err) != consts.CodeCouponUnavailable {
t.Fatalf("断言②失败:过期券应返 4011,实得 code=%d err=%v", codeOf(err), err)
}
}