package service // 管理端白盒测试:手动授权/撤销、订单分页、套餐更新、账号注册/登录。 // 运行方式同 payment_test.go:cd server && GF_GCFG_FILE=biz/service/testdata/config.yml go test ./biz/service/ import ( "strconv" "testing" "time" "github.com/gogf/gf/v2/os/gtime" "observer-server/biz/consts" "observer-server/biz/dao" "observer-server/biz/model/dto" "observer-server/biz/model/entity" "observer-server/common" ) func startOfToday() time.Time { now := time.Now() return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) } // TestAuthRegisterLogin 注册/登录:注册后可登录、密码错误拒绝、重复注册报错 func TestAuthRegisterLogin(t *testing.T) { phone := uniquePhone() if _, err := Auth.Register(ctx(), &dto.RegisterReq{Phone: phone, Password: "pass123456"}); err != nil { t.Fatalf("register: %v", err) } // 登录成功,token 可解析回手机号 res, err := Auth.Login(ctx(), &dto.LoginReq{Phone: phone, Password: "pass123456"}) if err != nil { t.Fatalf("login: %v", err) } got, err := common.ParseToken(ctx(), res.Token) if err != nil || got != phone { t.Fatalf("parse token = %q, %v; want %q", got, err, phone) } // 密码错误 if _, err := Auth.Login(ctx(), &dto.LoginReq{Phone: phone, Password: "wrong"}); err == nil { t.Fatal("wrong password should fail") } // 重复注册 if _, err := Auth.Register(ctx(), &dto.RegisterReq{Phone: phone, Password: "other123"}); err == nil { t.Fatal("duplicate register should fail") } // 未注册手机号登录自动创建账号并登录成功(自动注册式登录) phone2 := uniquePhone() res2, err := Auth.Login(ctx(), &dto.LoginReq{Phone: phone2, Password: "pass123456"}) if err != nil { t.Fatalf("login auto-register: %v", err) } got2, err := common.ParseToken(ctx(), res2.Token) if err != nil || got2 != phone2 { t.Fatalf("auto-register token = %q, %v; want %q", got2, err, phone2) } // 自动注册后已存在,重复注册报错 if _, err := Auth.Register(ctx(), &dto.RegisterReq{Phone: phone2, Password: "other123"}); err == nil { t.Fatal("register after auto-register should fail") } } // TestAuthLoginFillPlaceholder 运营发卡占位行(无密码)→ 登录用输入密码补写并成功,授权保留;错误密码被拒 func TestAuthLoginFillPlaceholder(t *testing.T) { phone := uniquePhone() if _, err := License.AdminGrant(ctx(), &dto.AdminGrantReq{PhoneNum: phone, PlanId: "day"}); err != nil { t.Fatalf("grant: %v", err) } if _, err := Auth.Login(ctx(), &dto.LoginReq{Phone: phone, Password: "pass123456"}); err != nil { t.Fatalf("login fill password: %v", err) } // 补密码不能清掉已授权 lic, err := dao.License.GetByPhone(ctx(), phone) if err != nil || lic == nil || lic.ExpiresAt == nil { t.Fatalf("grant lost after login fill password: %+v, %v", lic, err) } // 密码已写入:错误密码被拒 if _, err := Auth.Login(ctx(), &dto.LoginReq{Phone: phone, Password: "wrong"}); err == nil { t.Fatal("wrong password should fail after fill") } } // TestAuthRegisterAfterGrant 运营先手动授权(占位行无密码)→ 用户注册补密码 → 登录成功 func TestAuthRegisterAfterGrant(t *testing.T) { phone := uniquePhone() if _, err := License.AdminGrant(ctx(), &dto.AdminGrantReq{PhoneNum: phone, PlanId: "day"}); err != nil { t.Fatalf("grant: %v", err) } if _, err := Auth.Register(ctx(), &dto.RegisterReq{Phone: phone, Password: "pass123456"}); err != nil { t.Fatalf("register after grant: %v", err) } if _, err := Auth.Login(ctx(), &dto.LoginReq{Phone: phone, Password: "pass123456"}); err != nil { t.Fatalf("login after register: %v", err) } // 注册补密码不能清掉已授权 lic, err := dao.License.GetByPhone(ctx(), phone) if err != nil || lic == nil || lic.ExpiresAt == nil { t.Fatalf("grant lost after register: %+v, %v", lic, err) } } // TestAdminGrant 手动授权:新账号从今天 0 点起按套餐天数累计 func TestAdminGrant(t *testing.T) { phone := uniquePhone() res, err := License.AdminGrant(ctx(), &dto.AdminGrantReq{PhoneNum: phone, PlanId: "week"}) if err != nil { t.Fatalf("grant: %v", err) } want := startOfToday().AddDate(0, 0, 7) if !res.ExpiresAt.Time.Equal(want) { t.Fatalf("grant expiresAt = %s, want %s", res.ExpiresAt, want) } lic, err := License.Get(common.WithPhone(ctx(), phone), &dto.LicenseReq{}) if err != nil { t.Fatal(err) } if !lic.Active { t.Fatalf("license res = %+v", lic) } } // TestAdminGrantRenewal 手动授权续期叠加:未过期顺延;无效套餐报错 func TestAdminGrantRenewal(t *testing.T) { phone := uniquePhone() if _, err := License.AdminGrant(ctx(), &dto.AdminGrantReq{PhoneNum: phone, PlanId: "week"}); err != nil { t.Fatalf("grant 1: %v", err) } lic, _ := dao.License.GetByPhone(ctx(), phone) res, err := License.AdminGrant(ctx(), &dto.AdminGrantReq{PhoneNum: phone, PlanId: "month"}) if err != nil { t.Fatalf("grant 2: %v", err) } want := lic.ExpiresAt.Time.AddDate(0, 0, 30) if !res.ExpiresAt.Time.Equal(want) { t.Fatalf("renewal expiresAt = %s, want %s (base %s)", res.ExpiresAt, want, lic.ExpiresAt) } if _, err := License.AdminGrant(ctx(), &dto.AdminGrantReq{PhoneNum: phone, PlanId: "nope"}); err == nil { t.Fatal("invalid plan should fail") } } // TestAdminRevoke 撤销授权:清授权字段保留账号(可继续登录),授权查询立即 inactive func TestAdminRevoke(t *testing.T) { phone := uniquePhone() if _, err := Auth.Register(ctx(), &dto.RegisterReq{Phone: phone, Password: "pass123456"}); err != nil { t.Fatalf("register: %v", err) } if _, err := License.AdminGrant(ctx(), &dto.AdminGrantReq{PhoneNum: phone, PlanId: "day"}); err != nil { t.Fatalf("grant: %v", err) } if _, err := License.AdminRevoke(ctx(), &dto.AdminRevokeReq{PhoneNum: phone}); err != nil { t.Fatalf("revoke: %v", err) } lic, err := License.Get(common.WithPhone(ctx(), phone), &dto.LicenseReq{}) if err != nil { t.Fatal(err) } if lic.Active { t.Fatal("license should be inactive after revoke") } row, err := dao.License.GetByPhone(ctx(), phone) if err != nil { t.Fatal(err) } if row == nil || row.ExpiresAt != nil { t.Fatalf("account row should be kept with auth cleared: %+v", row) } // 账号仍可登录(密码未删) if _, err := Auth.Login(ctx(), &dto.LoginReq{Phone: phone, Password: "pass123456"}); err != nil { t.Fatalf("login after revoke: %v", err) } } // TestAdminListOrders 订单分页与筛选:总数、渠道筛选、分页截断 func TestAdminListOrders(t *testing.T) { phone := uniquePhone() orderId := uniqueOrderId("O-list-2-") for i, channel := range []string{consts.ChannelWechat, consts.ChannelWechat, consts.ChannelAlipay} { insertOrder(t, &entity.PaymentOrder{ OrderId: uniqueOrderId("O-list-" + strconv.Itoa(i) + "-"), PhoneNum: phone, PlanId: "day", Channel: channel, AmountCents: 1000, Status: consts.OrderStatusCreated, CreatedAt: gtime.Now(), }) } insertOrder(t, &entity.PaymentOrder{ OrderId: orderId, PhoneNum: phone, PlanId: "day", Channel: consts.ChannelWechat, AmountCents: 1000, Status: consts.OrderStatusCreated, CreatedAt: gtime.Now(), }) res, err := Order.AdminListOrders(ctx(), &dto.AdminOrderListReq{PhoneNum: phone, Size: 2}) if err != nil { t.Fatalf("list orders: %v", err) } if res.Total != 4 || len(res.List) != 2 { t.Fatalf("total=%d len=%d, want 4/2", res.Total, len(res.List)) } } // TestListPlans 套餐列表:来自 testdata/config.yml plans 节点(配置驱动,无数据库表) func TestListPlans(t *testing.T) { res, err := Order.ListPlans(ctx(), &dto.ListPlansReq{}) if err != nil { t.Fatalf("list plans: %v", err) } if len(res.List) != 3 { t.Fatalf("plans = %d, want 3", len(res.List)) } var week *dto.PlanItem for _, p := range res.List { if p.PlanId == "week" { week = p } } if week == nil || week.Days != 7 || week.PriceCents != 5600 { t.Fatalf("week plan = %+v, want days=7 priceCents=5600", week) } }