187 lines
6.0 KiB
Go
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)
|
|
}
|
|
}
|