Files
affiliate_dash/backend/internal/service/callback_test.go
T
yml2213 941e7ad79a 积分充值全流程落地:申请审核入账、凭证上传压缩转webp、管理员调账收权
- 新增积分充值申请与审核:商户提交充值申请(凭证图片必填,1元=100积分),
  管理员审核通过后事务内自动入账,幂等键防重复
- 新增通用图片上传组件 ImageUploader:canvas 压缩转 webp、预览、删除,
  后端按魔数校验图片格式并持久化到 data/uploads
- 管理员钱包调整改为按商户维度:移除商户侧调账接口,新增管理员
  平台商户积分调整与钱包流水查看
- 低余额 Webhook 告警配置持久化,充值入账/调账后自动检查推送
- 平台商户操作列移除进入/成员,积分充值页接入真实接口并修复金额单位换算
2026-08-04 13:03:29 +08:00

146 lines
4.6 KiB
Go

package service
import (
"testing"
"affiliate_dash/internal/model"
"gorm.io/gorm"
)
func TestCallbackSubscriptionSaveReusesSingleConfigAndControlsDelivery(t *testing.T) {
db := newServiceTestDB(t)
merchantID, _ := seedFulfillmentMerchant(t, db, "callback-single-config", 1000, 1, 100)
codec, err := NewSecretCodec("test-master-key")
if err != nil {
t.Fatalf("codec: %v", err)
}
svc := NewCallbackService(db, codec)
first, err := svc.CreateSubscription(merchantID, CreateCallbackInput{
URL: "https://example.com/old-callback",
Events: "order.shipping.updated",
Status: model.CallbackStatusActive,
}, 7)
if err != nil {
t.Fatalf("create callback: %v", err)
}
if first.Secret == "" {
t.Fatalf("new callback should return secret once")
}
second, err := svc.CreateSubscription(merchantID, CreateCallbackInput{
URL: "https://example.com/new-callback",
Events: "order.shipping.updated",
Status: model.CallbackStatusDisabled,
}, 7)
if err != nil {
t.Fatalf("update callback: %v", err)
}
if second.Subscription.ID != first.Subscription.ID {
t.Fatalf("callback should reuse one row, first=%d second=%d", first.Subscription.ID, second.Subscription.ID)
}
if second.Secret != "" {
t.Fatalf("updated callback should not rotate or reveal secret")
}
var subscriptionCount int64
if err := db.Model(&model.CallbackSubscription{}).Where("merchant_id = ?", merchantID).Count(&subscriptionCount).Error; err != nil {
t.Fatalf("count subscriptions: %v", err)
}
if subscriptionCount != 1 {
t.Fatalf("expected one callback subscription, got %d", subscriptionCount)
}
if err := db.Transaction(func(tx *gorm.DB) error {
return svc.Enqueue(tx, merchantID, "order.shipping.updated", map[string]string{"source": "test"})
}); err != nil {
t.Fatalf("enqueue disabled callback: %v", err)
}
var deliveryCount int64
if err := db.Model(&model.CallbackDelivery{}).Where("merchant_id = ?", merchantID).Count(&deliveryCount).Error; err != nil {
t.Fatalf("count deliveries: %v", err)
}
if deliveryCount != 0 {
t.Fatalf("disabled callback should not enqueue delivery, got %d", deliveryCount)
}
third, err := svc.CreateSubscription(merchantID, CreateCallbackInput{
URL: "https://example.com/new-callback",
Events: "order.shipping.updated",
Status: model.CallbackStatusActive,
}, 7)
if err != nil {
t.Fatalf("enable callback: %v", err)
}
if third.Subscription.ID != first.Subscription.ID {
t.Fatalf("enabled callback should still reuse one row")
}
if err := db.Transaction(func(tx *gorm.DB) error {
return svc.Enqueue(tx, merchantID, "order.shipping.updated", map[string]string{"source": "test"})
}); err != nil {
t.Fatalf("enqueue active callback: %v", err)
}
if err := db.Model(&model.CallbackDelivery{}).Where("merchant_id = ?", merchantID).Count(&deliveryCount).Error; err != nil {
t.Fatalf("count deliveries after enable: %v", err)
}
if deliveryCount != 1 {
t.Fatalf("active callback should enqueue one delivery, got %d", deliveryCount)
}
}
func TestCallbackSubscriptionRotateSecret(t *testing.T) {
db := newServiceTestDB(t)
merchantID, _ := seedFulfillmentMerchant(t, db, "callback-rotate-secret", 1000, 1, 100)
codec, err := NewSecretCodec("test-master-key")
if err != nil {
t.Fatalf("codec: %v", err)
}
svc := NewCallbackService(db, codec)
first, err := svc.CreateSubscription(merchantID, CreateCallbackInput{
URL: "https://example.com/cb",
Events: "order.shipping.updated",
Status: model.CallbackStatusActive,
}, 7)
if err != nil {
t.Fatalf("create callback: %v", err)
}
if first.Secret == "" {
t.Fatalf("new callback should return secret")
}
// 普通更新不轮换密钥
updated, err := svc.CreateSubscription(merchantID, CreateCallbackInput{
URL: "https://example.com/cb",
Events: "order.shipping.updated",
Status: model.CallbackStatusActive,
}, 7)
if err != nil {
t.Fatalf("update callback: %v", err)
}
if updated.Secret != "" {
t.Fatalf("plain update should not rotate secret")
}
// 显式重置密钥:返回新 secret,且与旧 secret 不同
rotated, err := svc.CreateSubscription(merchantID, CreateCallbackInput{
URL: "https://example.com/cb",
Events: "order.shipping.updated",
Status: model.CallbackStatusActive,
RotateSecret: true,
}, 7)
if err != nil {
t.Fatalf("rotate callback: %v", err)
}
if rotated.Secret == "" {
t.Fatalf("rotate should return new secret")
}
if rotated.Secret == first.Secret {
t.Fatalf("rotated secret should differ from old secret")
}
if rotated.Subscription.ID != first.Subscription.ID {
t.Fatalf("rotate should keep same subscription row")
}
}