244 lines
7.8 KiB
Go
244 lines
7.8 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
|
|
"affiliate_dash/internal/model"
|
|
)
|
|
|
|
func createDeleteTestToken(t *testing.T, svc *RockService, upstreamID, status string) model.RockToken {
|
|
t.Helper()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: upstreamID,
|
|
Token: "tk-" + upstreamID,
|
|
RedeemCode: "rc-" + upstreamID,
|
|
Color: model.RockColorBlack,
|
|
Status: status,
|
|
}
|
|
if status == model.RockStatusUsing || status == model.RockStatusBound || status == model.RockStatusMismatch {
|
|
token.AssignedOrderNo = "ORDER-" + upstreamID
|
|
}
|
|
if err := svc.db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
return token
|
|
}
|
|
|
|
func TestDeleteRockTokens_SoftDeletesAndHidesFromList(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
|
|
available := createDeleteTestToken(t, svc, "90001", model.RockStatusAvailable)
|
|
redeemed := createDeleteTestToken(t, svc, "90002", model.RockStatusRedeemed)
|
|
|
|
result, err := svc.DeleteRockTokens(ctx, []uint{available.ID}, false, 7)
|
|
if err != nil {
|
|
t.Fatalf("删除 Token 失败: %v", err)
|
|
}
|
|
if result.Deleted != 1 || result.Requested != 1 {
|
|
t.Fatalf("删除数量不符: %+v", result)
|
|
}
|
|
|
|
_, total, err := svc.ListTokens(ctx, 1, 20, "", "")
|
|
if err != nil {
|
|
t.Fatalf("查询列表失败: %v", err)
|
|
}
|
|
if total != 1 {
|
|
t.Fatalf("软删除后列表应只剩 1 条,实际 %d", total)
|
|
}
|
|
|
|
stats, err := svc.GetStats(ctx)
|
|
if err != nil {
|
|
t.Fatalf("查询统计失败: %v", err)
|
|
}
|
|
if stats.AvailableStock != 0 || stats.RedeemedStock != 1 {
|
|
t.Fatalf("统计未排除软删除: available=%d redeemed=%d", stats.AvailableStock, stats.RedeemedStock)
|
|
}
|
|
|
|
var all []model.RockToken
|
|
if err := db.Unscoped().Order("id ASC").Find(&all).Error; err != nil {
|
|
t.Fatalf("Unscoped 查询失败: %v", err)
|
|
}
|
|
if len(all) != 2 {
|
|
t.Fatalf("软删除不应物理删除,实际记录数 %d", len(all))
|
|
}
|
|
var deleted model.RockToken
|
|
if err := db.Unscoped().First(&deleted, available.ID).Error; err != nil {
|
|
t.Fatalf("读取已删除记录失败: %v", err)
|
|
}
|
|
if !deleted.DeletedAt.Valid {
|
|
t.Fatal("available Token 未写入 deleted_at")
|
|
}
|
|
|
|
var active model.RockToken
|
|
if err := db.First(&active, available.ID).Error; err == nil {
|
|
t.Fatal("普通查询不应返回已软删除 Token")
|
|
}
|
|
_ = redeemed
|
|
}
|
|
|
|
func TestDeleteRockTokens_RequiresForceForDangerousStatuses(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
|
|
using := createDeleteTestToken(t, svc, "91001", model.RockStatusUsing)
|
|
bound := createDeleteTestToken(t, svc, "91002", model.RockStatusBound)
|
|
mismatch := createDeleteTestToken(t, svc, "91003", model.RockStatusMismatch)
|
|
ids := []uint{using.ID, bound.ID, mismatch.ID}
|
|
|
|
if _, err := svc.DeleteRockTokens(ctx, ids, false, 7); err == nil {
|
|
t.Fatal("包含危险状态时未强制确认应失败")
|
|
} else {
|
|
var forceErr *RockTokenForceRequiredError
|
|
if !errors.As(err, &forceErr) {
|
|
t.Fatalf("错误类型不是 ForceRequired: %T %v", err, err)
|
|
}
|
|
if forceErr.Dangerous != 3 {
|
|
t.Fatalf("危险数量不符: %d", forceErr.Dangerous)
|
|
}
|
|
}
|
|
|
|
var activeCount int64
|
|
if err := db.Model(&model.RockToken{}).Count(&activeCount).Error; err != nil {
|
|
t.Fatalf("统计失败: %v", err)
|
|
}
|
|
if activeCount != 3 {
|
|
t.Fatalf("强制确认失败时不应删除,实际剩余 %d", activeCount)
|
|
}
|
|
|
|
result, err := svc.DeleteRockTokens(ctx, ids, true, 7)
|
|
if err != nil {
|
|
t.Fatalf("强制删除失败: %v", err)
|
|
}
|
|
if result.Deleted != 3 {
|
|
t.Fatalf("强制删除数量不符: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestPreviewAndDeleteAllRockTokens_Filtered(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
|
|
createDeleteTestToken(t, svc, "92001", model.RockStatusRedeemed)
|
|
createDeleteTestToken(t, svc, "92002", model.RockStatusRedeemed)
|
|
createDeleteTestToken(t, svc, "92003", model.RockStatusAvailable)
|
|
|
|
preview, err := svc.PreviewRockTokenDelete(ctx, model.RockStatusRedeemed, "")
|
|
if err != nil {
|
|
t.Fatalf("预览失败: %v", err)
|
|
}
|
|
if preview.Total != 2 || preview.Dangerous != 0 || preview.Available != 0 {
|
|
t.Fatalf("预览数量不符: %+v", preview)
|
|
}
|
|
|
|
// 预览数量与当前数据不一致时必须拒绝,避免误删。
|
|
if _, err := svc.DeleteAllRockTokens(ctx, model.RockStatusRedeemed, "", 1, false, 7); err == nil {
|
|
t.Fatal("expected_count 不一致时应拒绝")
|
|
} else {
|
|
var conflict *RockTokenDeleteConflictError
|
|
if !errors.As(err, &conflict) {
|
|
t.Fatalf("错误类型不是 Conflict: %T %v", err, err)
|
|
}
|
|
}
|
|
|
|
result, err := svc.DeleteAllRockTokens(ctx, model.RockStatusRedeemed, "", 2, false, 7)
|
|
if err != nil {
|
|
t.Fatalf("删除全部失败: %v", err)
|
|
}
|
|
if result.Deleted != 2 {
|
|
t.Fatalf("删除数量不符: %+v", result)
|
|
}
|
|
|
|
_, total, err := svc.ListTokens(ctx, 1, 20, "", "")
|
|
if err != nil {
|
|
t.Fatalf("查询列表失败: %v", err)
|
|
}
|
|
if total != 1 {
|
|
t.Fatalf("按状态删除后应剩 1 条 available,实际 %d", total)
|
|
}
|
|
}
|
|
|
|
func TestPreviewDeleteAll_ReportsDangerousAndAvailable(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
|
|
createDeleteTestToken(t, svc, "93001", model.RockStatusAvailable)
|
|
createDeleteTestToken(t, svc, "93002", model.RockStatusBound)
|
|
createDeleteTestToken(t, svc, "93003", model.RockStatusRedeemed)
|
|
|
|
preview, err := svc.PreviewRockTokenDelete(ctx, "", "")
|
|
if err != nil {
|
|
t.Fatalf("预览失败: %v", err)
|
|
}
|
|
if preview.Total != 3 || preview.Dangerous != 1 || preview.Available != 1 || !preview.RequiresForce {
|
|
t.Fatalf("预览结果不符: %+v", preview)
|
|
}
|
|
if len(preview.DangerousItems) != 1 || preview.DangerousItems[0].Status != model.RockStatusBound {
|
|
t.Fatalf("危险明细不符: %+v", preview.DangerousItems)
|
|
}
|
|
}
|
|
|
|
func TestImportPackageDetail_DoesNotResurrectDeletedToken(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
|
|
packageID := int64(12345)
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "94001",
|
|
Token: "tk-94001",
|
|
RedeemCode: "rc-94001",
|
|
Color: model.RockColorBlack,
|
|
Status: model.RockStatusAvailable,
|
|
PackageID: &packageID,
|
|
Source: "package",
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
if _, err := svc.DeleteRockTokens(ctx, []uint{token.ID}, false, 7); err != nil {
|
|
t.Fatalf("软删除失败: %v", err)
|
|
}
|
|
|
|
var detail RockPackageDetailResponse
|
|
raw := `{
|
|
"package": {"id": 12345, "share_code": "share-test"},
|
|
"tokens": [
|
|
{"id": 94001, "token": "tk-94001", "redeem_code": "rc-94001", "color": "black", "status": "available", "created_at": "2026-10-31 10:00:00"}
|
|
]
|
|
}`
|
|
if err := json.Unmarshal([]byte(raw), &detail); err != nil {
|
|
t.Fatalf("解析包详情失败: %v", err)
|
|
}
|
|
|
|
inserted, updated, skipped, err := svc.importPackageDetail(ctx, &detail, nil)
|
|
if err != nil {
|
|
t.Fatalf("导入包详情失败: %v", err)
|
|
}
|
|
if inserted != 0 || updated != 0 || skipped != 1 {
|
|
t.Fatalf("已删除 Token 不应被导入: inserted=%d updated=%d skipped=%d", inserted, updated, skipped)
|
|
}
|
|
|
|
var activeCount int64
|
|
if err := db.Model(&model.RockToken{}).Where("upstream_token_id = ?", "94001").Count(&activeCount).Error; err != nil {
|
|
t.Fatalf("统计失败: %v", err)
|
|
}
|
|
if activeCount != 0 {
|
|
t.Fatalf("已删除 Token 被重新创建,active=%d", activeCount)
|
|
}
|
|
var total int64
|
|
if err := db.Unscoped().Model(&model.RockToken{}).Where("upstream_token_id = ?", "94001").Count(&total).Error; err != nil {
|
|
t.Fatalf("Unscoped 统计失败: %v", err)
|
|
}
|
|
if total != 1 {
|
|
t.Fatalf("软删除记录应保留一条,实际 %d", total)
|
|
}
|
|
}
|