Files
2026-10-31 14:03:04 +08:00

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))
}
}