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