Files
affiliate_dash/backend/internal/service/rock_overdue_test.go
T

187 lines
6.0 KiB
Go

package service
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
"affiliate_dash/internal/model"
)
func TestUpdateConfig_BoundWarnDaysAndAutoRefresh(t *testing.T) {
db := newRockTestDB(t)
svc := NewRockService(db, NewRockClient("https://example.com"))
ctx := context.Background()
cfg, err := svc.GetConfig(ctx)
if err != nil {
t.Fatalf("获取配置失败: %v", err)
}
if cfg.BoundWarnDays != 2 {
t.Fatalf("绑定过期预警默认值应为 2,实际 %d", cfg.BoundWarnDays)
}
if cfg.AutoRefreshMismatchEnabled {
t.Fatalf("自动刷新开关默认必须关闭")
}
zero := 0
if _, err := svc.UpdateConfig(ctx, UpdateRockConfigReq{BoundWarnDays: &zero}); err == nil {
t.Fatalf("绑定过期预警天数小于 1 时必须报错")
}
tooMany := 366
if _, err := svc.UpdateConfig(ctx, UpdateRockConfigReq{BoundWarnDays: &tooMany}); err == nil {
t.Fatalf("绑定过期预警天数大于 365 时必须报错")
}
seven := 7
enabled := true
updated, err := svc.UpdateConfig(ctx, UpdateRockConfigReq{
BoundWarnDays: &seven,
AutoRefreshMismatchEnabled: &enabled,
})
if err != nil {
t.Fatalf("保存绑定过期预警失败: %v", err)
}
if updated.BoundWarnDays != 7 || !updated.AutoRefreshMismatchEnabled {
t.Fatalf("绑定过期预警/自动刷新未保存: %+v", updated)
}
}
func TestListTokens_OverdueFilters(t *testing.T) {
db := newRockTestDB(t)
svc := NewRockService(db, NewRockClient("https://example.com"))
ctx := context.Background()
now := time.Now()
old := now.AddDate(0, 0, -10)
fresh := now.Add(-time.Hour)
usingOld := model.RockToken{UpstreamTokenID: "overdue-using", RedeemCode: "C1", Status: model.RockStatusUsing, UsingAt: &old}
usingFresh := model.RockToken{UpstreamTokenID: "fresh-using", RedeemCode: "C2", Status: model.RockStatusUsing, UsingAt: &fresh}
boundOld := model.RockToken{UpstreamTokenID: "overdue-bound", RedeemCode: "C3", Status: model.RockStatusBound, BoundAt: &old}
boundFresh := model.RockToken{UpstreamTokenID: "fresh-bound", RedeemCode: "C4", Status: model.RockStatusBound, BoundAt: &fresh}
redeemedOld := model.RockToken{UpstreamTokenID: "old-redeemed", RedeemCode: "C5", Status: model.RockStatusRedeemed, BoundAt: &old}
for _, token := range []*model.RockToken{&usingOld, &usingFresh, &boundOld, &boundFresh, &redeemedOld} {
if err := db.Create(token).Error; err != nil {
t.Fatalf("创建 Token 失败: %v", err)
}
}
cases := []struct {
status string
wantID uint
}{
{rockTokenStatusUsingOverdue, usingOld.ID},
{rockTokenStatusBoundOverdue, boundOld.ID},
}
for _, tc := range cases {
list, total, err := svc.ListTokens(ctx, 1, 20, tc.status, "")
if err != nil {
t.Fatalf("查询 %s 失败: %v", tc.status, err)
}
if total != 1 || len(list) != 1 || list[0].ID != tc.wantID {
t.Fatalf("%s 应只返回 ID=%d,实际 total=%d list=%+v", tc.status, tc.wantID, total, list)
}
}
stats, err := svc.GetStats(ctx)
if err != nil {
t.Fatalf("获取统计失败: %v", err)
}
if stats.UsingOverdueStock != 1 || stats.UsingDangerStock != 1 || stats.BoundOverdueStock != 1 {
t.Fatalf("过期预警统计不符: using_overdue=%d using_danger=%d bound_overdue=%d",
stats.UsingOverdueStock, stats.UsingDangerStock, stats.BoundOverdueStock)
}
}
func TestRefreshMismatchTokens_SkipsWhenAutoRefreshDisabled(t *testing.T) {
db := newRockTestDB(t)
var calls int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&calls, 1)
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
svc := NewRockService(db, NewRockClient(server.URL))
setRockTestBearer(t, svc, "bearer-test")
token := model.RockToken{
UpstreamTokenID: "scan-off",
RedeemCode: "CODE-SCAN-OFF",
Status: model.RockStatusMismatch,
}
if err := db.Create(&token).Error; err != nil {
t.Fatalf("创建 Token 失败: %v", err)
}
if err := db.Model(&model.RockToken{}).Where("id = ?", token.ID).
UpdateColumn("updated_at", time.Now().Add(-time.Hour)).Error; err != nil {
t.Fatalf("调整 updated_at 失败: %v", err)
}
svc.RefreshMismatchTokens(context.Background())
if atomic.LoadInt32(&calls) != 0 {
t.Fatalf("自动刷新关闭时不应调用上游")
}
var saved model.RockToken
if err := db.First(&saved, token.ID).Error; err != nil {
t.Fatalf("读取 Token 失败: %v", err)
}
if saved.Status != model.RockStatusMismatch {
t.Fatalf("自动刷新关闭时应保持 mismatch,实际 %s", saved.Status)
}
}
func TestRefreshAllMismatchTokens_FixesMismatch(t *testing.T) {
db := newRockTestDB(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"token": map[string]interface{}{
"status": "bound",
"bound_account": "痛写心扉",
"bound_account_id": "3132710334",
},
})
}))
defer server.Close()
svc := NewRockService(db, NewRockClient(server.URL))
setRockTestBearer(t, svc, "bearer-test")
token := model.RockToken{
UpstreamTokenID: "scan-on",
RedeemCode: "CODE-SCAN-ON",
Status: model.RockStatusMismatch,
ExpectedUID: "1111111111",
}
if err := db.Create(&token).Error; err != nil {
t.Fatalf("创建 Token 失败: %v", err)
}
if err := db.Model(&model.RockToken{}).Where("id = ?", token.ID).
UpdateColumn("updated_at", time.Now().Add(-time.Hour)).Error; err != nil {
t.Fatalf("调整 updated_at 失败: %v", err)
}
result, err := svc.RefreshAllMismatchTokens(context.Background())
if err != nil {
t.Fatalf("一键刷新失败: %v", err)
}
if result.Scanned != 1 || result.Fixed != 1 || result.Remaining != 0 || result.Failed != 0 {
t.Fatalf("一键刷新结果不符: %+v", result)
}
var saved model.RockToken
if err := db.First(&saved, token.ID).Error; err != nil {
t.Fatalf("读取 Token 失败: %v", err)
}
if saved.Status != model.RockStatusBound || saved.MismatchAt != nil {
t.Fatalf("应修复为 bound 并清空 mismatch 标记,实际 %+v", saved)
}
}