//go:build integration // +build integration package integration import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/api" "veza-backend-api/internal/config" "veza-backend-api/internal/core/marketplace" "veza-backend-api/internal/database" "veza-backend-api/internal/metrics" "veza-backend-api/internal/models" "veza-backend-api/internal/services" ) // mockRefundPaymentProvider implements PaymentProvider and Refund for refund flow test type mockRefundPaymentProvider struct { refundErr error } func (m *mockRefundPaymentProvider) CreatePayment(_ context.Context, _ int64, _ string, _ string, _ string, _ map[string]string) (string, string, error) { return "pay_refund_mock", "secret", nil } func (m *mockRefundPaymentProvider) GetPayment(_ context.Context, _ string) (string, error) { return "succeeded", nil } func (m *mockRefundPaymentProvider) Refund(_ context.Context, _ string, _ *int64, _ string) error { return m.refundErr } func setupRefundFlowRouter(t *testing.T, db *gorm.DB, marketService *marketplace.Service) *gin.Engine { t.Helper() os.Setenv("ENABLE_CLAMAV", "false") os.Setenv("CLAMAV_REQUIRED", "false") gin.SetMode(gin.TestMode) router := gin.New() sqlDB, err := db.DB() require.NoError(t, err) vezaDB := &database.Database{ DB: sqlDB, GormDB: db, Logger: zap.NewNop(), } cfg := &config.Config{ HyperswitchWebhookSecret: "test-secret", JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", JWTIssuer: "veza-api", JWTAudience: "veza-app", Logger: zap.NewNop(), RedisClient: nil, ErrorMetrics: metrics.NewErrorMetrics(), UploadDir: "uploads/test", Env: "development", Database: vezaDB, CORSOrigins: []string{"*"}, HandlerTimeout: 30 * time.Second, RateLimitLimit: 100, RateLimitWindow: 60, AuthRateLimitLoginAttempts: 10, AuthRateLimitLoginWindow: 15, MarketplaceServiceOverride: marketService, AuthMiddlewareOverride: &testAuthMiddleware{}, } require.NoError(t, cfg.InitServicesForTest()) require.NoError(t, cfg.InitMiddlewaresForTest()) apiRouter := api.NewAPIRouter(vezaDB, cfg) require.NoError(t, apiRouter.Setup(router)) return router } // TestRefundFlow_CompletedOrder_Refund_RevokesLicense verifies: order completed -> refund request -> // order status refunded, licence revoked. func TestRefundFlow_CompletedOrder_Refund_RevokesLicense(t *testing.T) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) require.NoError(t, db.AutoMigrate( &models.User{}, &models.Track{}, &marketplace.Product{}, &marketplace.Order{}, &marketplace.OrderItem{}, &marketplace.License{}, &marketplace.SellerTransfer{}, )) buyerID := uuid.New() sellerID := uuid.New() trackID := uuid.New() productID := uuid.New() orderID := uuid.New() require.NoError(t, db.Create(&models.User{ID: buyerID}).Error) require.NoError(t, db.Create(&models.User{ID: sellerID}).Error) require.NoError(t, db.Create(&models.Track{ID: trackID, UserID: sellerID, FilePath: "/t.mp3"}).Error) require.NoError(t, db.Create(&marketplace.Product{ ID: productID, SellerID: sellerID, Title: "Test Product", Price: 9.99, ProductType: "track", TrackID: &trackID, Status: marketplace.ProductStatusActive, }).Error) require.NoError(t, db.Create(&marketplace.Order{ ID: orderID, BuyerID: buyerID, TotalAmount: 9.99, Currency: "EUR", Status: "completed", HyperswitchPaymentID: "pay_refund_mock", }).Error) require.NoError(t, db.Create(&marketplace.OrderItem{ ID: uuid.New(), OrderID: orderID, ProductID: productID, Price: 9.99, }).Error) require.NoError(t, db.Create(&marketplace.License{ ID: uuid.New(), BuyerID: buyerID, TrackID: trackID, ProductID: productID, OrderID: orderID, Type: marketplace.LicenseBasic, }).Error) mockPay := &mockRefundPaymentProvider{} storageService := services.NewTrackStorageService("uploads/test", false, zap.NewNop()) marketService := marketplace.NewService(db, zap.NewNop(), storageService, marketplace.WithPaymentProvider(mockPay)) router := setupRefundFlowRouter(t, db, marketService) body, _ := json.Marshal(map[string]string{"reason": "Customer request"}) req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/orders/"+orderID.String()+"/refund", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-User-ID", buyerID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code, "refund must succeed: %s", w.Body.String()) var order marketplace.Order require.NoError(t, db.First(&order, orderID).Error) assert.Equal(t, "refunded", order.Status) var licenses []marketplace.License require.NoError(t, db.Where("order_id = ?", orderID).Find(&licenses).Error) require.Len(t, licenses, 1) assert.NotNil(t, licenses[0].RevokedAt, "license must be revoked") }