250 lines
9.6 KiB
Go
250 lines
9.6 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"affiliate_dash/internal/model"
|
|
)
|
|
|
|
func newImportAllMock(t *testing.T, packages []map[string]interface{}, details map[string]map[string]interface{}) *httptest.Server {
|
|
t.Helper()
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch {
|
|
case r.URL.Path == "/api/user/packages":
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"packages": packages,
|
|
"pagination": map[string]interface{}{
|
|
"page": 1, "pageSize": 100, "total": len(packages), "totalPages": 1,
|
|
},
|
|
})
|
|
case strings.HasPrefix(r.URL.Path, "/api/package/"):
|
|
code := strings.TrimPrefix(r.URL.Path, "/api/package/")
|
|
detail, ok := details[code]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
_, _ = w.Write([]byte(`{"error":"package not found"}`))
|
|
return
|
|
}
|
|
_ = json.NewEncoder(w).Encode(detail)
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
}
|
|
|
|
func TestIsUnboundRockToken(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
token RockPackageToken
|
|
expect bool
|
|
}{
|
|
{"两个绑定字段都为空", RockPackageToken{BindChangedAt: "", BindChangeType: ""}, true},
|
|
{"有 bind_changed_at", RockPackageToken{BindChangedAt: "2026-10-30 16:02:19", BindChangeType: ""}, false},
|
|
{"bind 类型", RockPackageToken{BindChangedAt: "", BindChangeType: "bind"}, false},
|
|
{"rebind 类型", RockPackageToken{BindChangedAt: "", BindChangeType: "rebind"}, false},
|
|
{"两者都有", RockPackageToken{BindChangedAt: "2026-10-30 16:02:19", BindChangeType: "rebind"}, false},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := isUnboundRockToken(tc.token); got != tc.expect {
|
|
t.Fatalf("%s: isUnboundRockToken=%v, want %v", tc.name, got, tc.expect)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestImportAllUpstreamPackages_SkipsImported(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
packages := []map[string]interface{}{
|
|
{"id": 100, "token_count": 1, "share_code": "share-100", "status": "active", "created_at": "2026-10-20 10:00:00", "note": "a"},
|
|
{"id": 101, "token_count": 1, "share_code": "share-101", "status": "active", "created_at": "2026-10-21 10:00:00", "note": "b"},
|
|
}
|
|
details := map[string]map[string]interface{}{
|
|
"share-101": {
|
|
"package": map[string]interface{}{"id": 101, "share_code": "share-101", "token_count": 1, "status": "active"},
|
|
"tokens": []map[string]interface{}{
|
|
{"id": 95001, "token": "tk-95001", "color": "black", "region": "Q/V可用", "status": "using", "redeem_code": "rc-95001"},
|
|
},
|
|
},
|
|
}
|
|
server := newImportAllMock(t, packages, details)
|
|
defer server.Close()
|
|
|
|
pid := int64(100)
|
|
if err := db.Create(&model.RockToken{
|
|
UpstreamTokenID: "90001",
|
|
Token: "tk-90001",
|
|
RedeemCode: "rc-90001",
|
|
Status: model.RockStatusAvailable,
|
|
PackageID: &pid,
|
|
}).Error; err != nil {
|
|
t.Fatalf("写入本地 Token 失败: %v", err)
|
|
}
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
result, err := svc.ImportAllUpstreamPackages(context.Background(), 50, 1)
|
|
if err != nil {
|
|
t.Fatalf("全量导入失败: %v", err)
|
|
}
|
|
if result.Scanned != 2 || result.Skipped != 1 || result.Imported != 1 {
|
|
t.Fatalf("汇总不符: %+v", result)
|
|
}
|
|
if result.TokensImported != 1 {
|
|
t.Fatalf("应导入 1 个 Token,实际 %d", result.TokensImported)
|
|
}
|
|
|
|
var token model.RockToken
|
|
if err := db.Where("upstream_token_id = ?", "95001").First(&token).Error; err != nil {
|
|
t.Fatalf("查询导入 Token 失败: %v", err)
|
|
}
|
|
if token.Status != model.RockStatusAvailable {
|
|
t.Fatalf("导入后应为 available,实际 %s", token.Status)
|
|
}
|
|
}
|
|
|
|
func TestImportAllUpstreamPackages_FiltersBoundTokens(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
packages := []map[string]interface{}{
|
|
{"id": 300, "token_count": 3, "share_code": "share-300", "status": "active", "created_at": "2026-10-22 10:00:00", "note": "c"},
|
|
}
|
|
details := map[string]map[string]interface{}{
|
|
"share-300": {
|
|
"package": map[string]interface{}{"id": 300, "share_code": "share-300", "token_count": 3, "status": "active"},
|
|
"tokens": []map[string]interface{}{
|
|
{"id": 96001, "token": "tk-96001", "color": "black", "region": "Q/V可用", "status": "using", "redeem_code": "rc-96001"},
|
|
{"id": 96002, "token": "tk-96002", "color": "black", "region": "Q/V可用", "status": "bound", "redeem_code": "rc-96002", "bind_changed_at": "2026-10-30 16:02:19", "bind_change_type": "bind", "bound_account": "角色A", "bound_account_id": "111"},
|
|
{"id": 96003, "token": "tk-96003", "color": "black", "region": "Q/V可用", "status": "redeemed", "redeem_code": "rc-96003", "bind_changed_at": "2026-10-31 12:45:06", "bind_change_type": "rebind", "bound_account": "角色B", "bound_account_id": "222"},
|
|
},
|
|
},
|
|
}
|
|
server := newImportAllMock(t, packages, details)
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
result, err := svc.ImportAllUpstreamPackages(context.Background(), 50, 1)
|
|
if err != nil {
|
|
t.Fatalf("全量导入失败: %v", err)
|
|
}
|
|
if result.Imported != 1 || result.TokensImported != 1 {
|
|
t.Fatalf("应只导入 1 个未绑定 Token,实际 %+v", result)
|
|
}
|
|
if result.TokensSkippedBound != 2 {
|
|
t.Fatalf("应跳过 2 个已绑定 Token,实际 %d", result.TokensSkippedBound)
|
|
}
|
|
|
|
var count int64
|
|
db.Model(&model.RockToken{}).Where("upstream_token_id IN ?", []string{"96002", "96003"}).Count(&count)
|
|
if count != 0 {
|
|
t.Fatalf("已绑定 Token 不应入库,实际 %d 条", count)
|
|
}
|
|
|
|
var token model.RockToken
|
|
if err := db.Where("upstream_token_id = ?", "96001").First(&token).Error; err != nil {
|
|
t.Fatalf("查询导入 Token 失败: %v", err)
|
|
}
|
|
if token.Status != model.RockStatusAvailable || token.UsingAt != nil || token.BoundAt != nil {
|
|
t.Fatalf("导入结果不符: status=%s usingAt=%v boundAt=%v", token.Status, token.UsingAt, token.BoundAt)
|
|
}
|
|
}
|
|
|
|
func TestImportAllUpstreamPackages_EmptyWhenAllBound(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
packages := []map[string]interface{}{
|
|
{"id": 310, "token_count": 1, "share_code": "share-310", "status": "active", "created_at": "2026-10-22 10:00:00", "note": "d"},
|
|
}
|
|
details := map[string]map[string]interface{}{
|
|
"share-310": {
|
|
"package": map[string]interface{}{"id": 310, "share_code": "share-310", "token_count": 1, "status": "active"},
|
|
"tokens": []map[string]interface{}{
|
|
{"id": 97001, "token": "tk-97001", "color": "black", "region": "Q/V可用", "status": "bound", "redeem_code": "rc-97001", "bind_changed_at": "2026-10-30 16:02:19", "bind_change_type": "bind"},
|
|
},
|
|
},
|
|
}
|
|
server := newImportAllMock(t, packages, details)
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
result, err := svc.ImportAllUpstreamPackages(context.Background(), 50, 1)
|
|
if err != nil {
|
|
t.Fatalf("全量导入失败: %v", err)
|
|
}
|
|
if result.Empty != 1 || result.Imported != 0 {
|
|
t.Fatalf("全部已绑定时应记为 empty,实际 %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestImportAllUpstreamPackages_PartialFailure(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
packages := []map[string]interface{}{
|
|
{"id": 400, "token_count": 1, "share_code": "share-400", "status": "active", "created_at": "2026-10-23 10:00:00", "note": "e"},
|
|
{"id": 401, "token_count": 1, "share_code": "share-401", "status": "active", "created_at": "2026-10-24 10:00:00", "note": "f"},
|
|
}
|
|
details := map[string]map[string]interface{}{
|
|
"share-401": {
|
|
"package": map[string]interface{}{"id": 401, "share_code": "share-401", "token_count": 1, "status": "active"},
|
|
"tokens": []map[string]interface{}{
|
|
{"id": 98001, "token": "tk-98001", "color": "black", "region": "Q/V可用", "status": "using", "redeem_code": "rc-98001"},
|
|
},
|
|
},
|
|
}
|
|
server := newImportAllMock(t, packages, details)
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
result, err := svc.ImportAllUpstreamPackages(context.Background(), 50, 1)
|
|
if err != nil {
|
|
t.Fatalf("全量导入调用失败: %v", err)
|
|
}
|
|
if result.Failed != 1 || result.Imported != 1 {
|
|
t.Fatalf("应 1 失败 1 成功,实际 %+v", result)
|
|
}
|
|
if len(result.Results) != 2 || result.Results[0].Error == "" {
|
|
t.Fatalf("失败包应带错误信息: %+v", result.Results)
|
|
}
|
|
}
|
|
|
|
func TestImportAllUpstreamPackages_MaxPackages(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
packages := []map[string]interface{}{
|
|
{"id": 500, "token_count": 1, "share_code": "share-500", "status": "active", "created_at": "2026-10-25 10:00:00", "note": "g"},
|
|
{"id": 501, "token_count": 1, "share_code": "share-501", "status": "active", "created_at": "2026-10-26 10:00:00", "note": "h"},
|
|
{"id": 502, "token_count": 1, "share_code": "share-502", "status": "active", "created_at": "2026-10-27 10:00:00", "note": "i"},
|
|
}
|
|
details := map[string]map[string]interface{}{
|
|
"share-500": {
|
|
"package": map[string]interface{}{"id": 500, "share_code": "share-500", "token_count": 1, "status": "active"},
|
|
"tokens": []map[string]interface{}{
|
|
{"id": 99001, "token": "tk-99001", "color": "black", "region": "Q/V可用", "status": "using", "redeem_code": "rc-99001"},
|
|
},
|
|
},
|
|
}
|
|
server := newImportAllMock(t, packages, details)
|
|
defer server.Close()
|
|
|
|
svc := NewRockService(db, NewRockClient(server.URL))
|
|
setRockTestBearer(t, svc, "bearer-test")
|
|
|
|
result, err := svc.ImportAllUpstreamPackages(context.Background(), 1, 1)
|
|
if err != nil {
|
|
t.Fatalf("全量导入失败: %v", err)
|
|
}
|
|
if result.Remaining != 2 {
|
|
t.Fatalf("上限 1 时应剩余 2 个包,实际 %d", result.Remaining)
|
|
}
|
|
if len(result.Results) != 1 {
|
|
t.Fatalf("应只处理 1 个包,实际 %d", len(result.Results))
|
|
}
|
|
}
|