378 lines
12 KiB
Go
378 lines
12 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/model"
|
|
)
|
|
|
|
func TestRefreshTokenBinding_BoundMatchesExpectedUID(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")
|
|
if r.URL.Path != "/api/redeem/CODE-BOUND/refresh" {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"token": map[string]interface{}{
|
|
"status": "bound",
|
|
"bound_account": "痛写心扉",
|
|
"bound_account_id": "3132710334",
|
|
"region": "安卓-微信",
|
|
},
|
|
"binding": map[string]interface{}{
|
|
"bound": true,
|
|
"role": "痛写心扉",
|
|
"roleId": "3132710334",
|
|
"region": "安卓-微信",
|
|
},
|
|
"message": "已刷新绑定信息",
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
now := time.Now()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "9001",
|
|
Token: "jwt-9001",
|
|
RedeemCode: "CODE-BOUND",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusUsing,
|
|
AssignedOrderNo: "FO-REFRESH-1",
|
|
ExpectedUID: "3132710334",
|
|
ClaimedAt: &now,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
|
|
res, err := svc.RefreshTokenBinding(context.Background(), token.ID)
|
|
if err != nil {
|
|
t.Fatalf("刷新绑定状态失败: %v", err)
|
|
}
|
|
if res.Status != model.RockStatusBound || !res.Bound {
|
|
t.Fatalf("期望 bound,实际 status=%s bound=%v", res.Status, res.Bound)
|
|
}
|
|
if res.RoleID != "3132710334" || res.RoleName != "痛写心扉" {
|
|
t.Fatalf("角色信息未同步: %+v", res)
|
|
}
|
|
|
|
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.ActualRoleID != "3132710334" || saved.ActualRoleName != "痛写心扉" {
|
|
t.Fatalf("数据库未更新: %+v", saved)
|
|
}
|
|
if saved.BoundAt == nil {
|
|
t.Fatalf("bound_at 未写入")
|
|
}
|
|
}
|
|
|
|
func TestRefreshTokenBinding_MarksMismatch(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",
|
|
"region": "安卓-微信",
|
|
},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
now := time.Now()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "9002",
|
|
RedeemCode: "CODE-MISMATCH",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusUsing,
|
|
AssignedOrderNo: "FO-REFRESH-2",
|
|
ExpectedUID: "1111111111",
|
|
ClaimedAt: &now,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
|
|
res, err := svc.RefreshTokenBinding(context.Background(), token.ID)
|
|
if err != nil {
|
|
t.Fatalf("刷新绑定状态失败: %v", err)
|
|
}
|
|
if res.Status != model.RockStatusMismatch || !strings.Contains(res.Message, "不一致") {
|
|
t.Fatalf("期望 mismatch 提示,实际 %+v", res)
|
|
}
|
|
|
|
var saved model.RockToken
|
|
if err := db.First(&saved, token.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if saved.Status != model.RockStatusMismatch || saved.MismatchAt == nil || !strings.Contains(saved.MismatchReason, "3132710334") {
|
|
t.Fatalf("数据库 mismatch 字段不符: %+v", saved)
|
|
}
|
|
}
|
|
|
|
func TestRefreshTokenBinding_EmptyExpectedUIDKeepsStatus(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{}{
|
|
"binding": map[string]interface{}{
|
|
"bound": true,
|
|
"role": "痛写心扉",
|
|
"roleId": "3132710334",
|
|
"region": "安卓-微信",
|
|
},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "9003",
|
|
RedeemCode: "CODE-NOUID",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusUsing,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
|
|
res, err := svc.RefreshTokenBinding(context.Background(), token.ID)
|
|
if err != nil {
|
|
t.Fatalf("刷新绑定状态失败: %v", err)
|
|
}
|
|
if res.Status != model.RockStatusUsing {
|
|
t.Fatalf("缺少预期 UID 时状态应保持 using,实际 %s", res.Status)
|
|
}
|
|
if !strings.Contains(res.Message, "人工核对") {
|
|
t.Fatalf("应提示人工核对,实际 %q", res.Message)
|
|
}
|
|
if !res.Warning {
|
|
t.Fatalf("缺少预期 UID 时应返回 warning=true")
|
|
}
|
|
|
|
var saved model.RockToken
|
|
if err := db.First(&saved, token.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if saved.Status != model.RockStatusUsing || saved.ActualRoleID != "3132710334" {
|
|
t.Fatalf("只应同步角色信息,实际 %+v", saved)
|
|
}
|
|
}
|
|
|
|
func TestRefreshTokenBinding_SyncsRedeemed(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": "redeemed",
|
|
"bound_account": "痛写心扉",
|
|
"bound_account_id": "3132710334",
|
|
"redeemed_at": "2026-10-29 12:00:00",
|
|
},
|
|
"deliveryList": []map[string]interface{}{
|
|
{"fulfillId": 4519960, "time": "2026-10-29T12:00:00.1", "role": "安卓-微信-痛写心扉"},
|
|
},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "9004",
|
|
RedeemCode: "CODE-REDEEMED",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusUsing,
|
|
AssignedOrderNo: "FO-REFRESH-4",
|
|
ExpectedUID: "3132710334",
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
|
|
res, err := svc.RefreshTokenBinding(context.Background(), token.ID)
|
|
if err != nil {
|
|
t.Fatalf("刷新绑定状态失败: %v", err)
|
|
}
|
|
if res.Status != model.RockStatusRedeemed {
|
|
t.Fatalf("期望 redeemed,实际 %s", res.Status)
|
|
}
|
|
|
|
var saved model.RockToken
|
|
if err := db.First(&saved, token.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if saved.Status != model.RockStatusRedeemed || saved.RedeemedAt == nil || saved.UpstreamFulfillID != 4519960 {
|
|
t.Fatalf("已发货字段未同步: %+v", saved)
|
|
}
|
|
}
|
|
|
|
func TestRefreshTokenBinding_UpstreamErrorKeepsState(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write([]byte(`{"error":"refresh down"}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "9005",
|
|
RedeemCode: "CODE-ERROR",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusUsing,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
|
|
if _, err := svc.RefreshTokenBinding(context.Background(), token.ID); err == nil {
|
|
t.Fatalf("上游 500 时应返回错误")
|
|
}
|
|
|
|
var saved model.RockToken
|
|
if err := db.First(&saved, token.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if saved.Status != model.RockStatusUsing || saved.ActualRoleID != "" {
|
|
t.Fatalf("失败时不应改动本地状态: %+v", saved)
|
|
}
|
|
}
|
|
|
|
func TestRefreshTokenBinding_FallsBackToAdminRefresh(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")
|
|
switch r.URL.Path {
|
|
case "/api/redeem/CODE-FALLBACK/refresh":
|
|
w.WriteHeader(http.StatusNotFound)
|
|
case "/api/user/tokens/9006/refresh":
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"token": map[string]interface{}{
|
|
"status": "bound",
|
|
"bound_account": "痛写心扉",
|
|
"bound_account_id": "3132710334",
|
|
},
|
|
})
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
now := time.Now()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "9006",
|
|
RedeemCode: "CODE-FALLBACK",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusUsing,
|
|
AssignedOrderNo: "FO-REFRESH-6",
|
|
ExpectedUID: "3132710334",
|
|
ClaimedAt: &now,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
|
|
res, err := svc.RefreshTokenBinding(context.Background(), token.ID)
|
|
if err != nil {
|
|
t.Fatalf("降级刷新失败: %v", err)
|
|
}
|
|
if res.Status != model.RockStatusBound {
|
|
t.Fatalf("降级后期望 bound,实际 %s", res.Status)
|
|
}
|
|
}
|
|
|
|
func TestRefreshTokenBinding_NotBoundKeepsRoleFields(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": "using",
|
|
"region": "Q/V可用",
|
|
},
|
|
"binding": map[string]interface{}{"bound": false},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "9008",
|
|
RedeemCode: "CODE-UNBOUND",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusUsing,
|
|
ActualRoleName: "旧角色",
|
|
ActualRoleID: "123456",
|
|
ActualChannel: "安卓-微信",
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
|
|
res, err := svc.RefreshTokenBinding(context.Background(), token.ID)
|
|
if err != nil {
|
|
t.Fatalf("刷新绑定状态失败: %v", err)
|
|
}
|
|
if res.Status != model.RockStatusUsing || res.Bound {
|
|
t.Fatalf("上游未绑定时应保持 using 且 bound=false,实际 %+v", res)
|
|
}
|
|
|
|
var saved model.RockToken
|
|
if err := db.First(&saved, token.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if saved.ActualRoleName != "旧角色" || saved.ActualRoleID != "123456" || saved.ActualChannel != "安卓-微信" {
|
|
t.Fatalf("上游未绑定时不应覆盖本地角色字段: %+v", saved)
|
|
}
|
|
}
|
|
|
|
func TestRefreshTokenBinding_RejectsAvailableToken(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "9007",
|
|
RedeemCode: "CODE-AVAILABLE",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusAvailable,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
|
|
if _, err := svc.RefreshTokenBinding(context.Background(), token.ID); err == nil || !strings.Contains(err.Error(), "仅扫码使用中") {
|
|
t.Fatalf("available 状态应被拒绝,实际 err=%v", err)
|
|
}
|
|
}
|