diff --git a/veza-backend-api/internal/core/marketplace/review_test.go b/veza-backend-api/internal/core/marketplace/review_test.go new file mode 100644 index 000000000..186fa3ee3 --- /dev/null +++ b/veza-backend-api/internal/core/marketplace/review_test.go @@ -0,0 +1,222 @@ +package marketplace + +import ( + "context" + "testing" + + "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/models" +) + +func setupReviewTestDB(t *testing.T) *gorm.DB { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + require.NoError(t, db.AutoMigrate( + &models.User{}, + &Product{}, + &Order{}, + &OrderItem{}, + &License{}, + &ProductReview{}, + &models.Track{}, + )) + return db +} + +func TestCreateReview_Success(t *testing.T) { + db := setupReviewTestDB(t) + logger := zap.NewNop() + svc := NewService(db, logger, nil) + + 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: "/test.mp3"}).Error) + require.NoError(t, db.Create(&Product{ + ID: productID, + SellerID: sellerID, + Title: "Test Product", + Price: 9.99, + ProductType: "track", + TrackID: &trackID, + Status: ProductStatusActive, + }).Error) + require.NoError(t, db.Create(&Order{ + ID: orderID, + BuyerID: buyerID, + Status: "completed", + TotalAmount: 9.99, + Currency: "EUR", + }).Error) + require.NoError(t, db.Create(&OrderItem{ + ID: uuid.New(), + OrderID: orderID, + ProductID: productID, + Price: 9.99, + }).Error) + require.NoError(t, db.Create(&License{ + BuyerID: buyerID, + TrackID: trackID, + ProductID: productID, + OrderID: orderID, + Type: LicenseBasic, + }).Error) + + review, err := svc.CreateReview(context.Background(), productID, buyerID, 5, "Great product!") + require.NoError(t, err) + require.NotNil(t, review) + assert.Equal(t, productID, review.ProductID) + assert.Equal(t, buyerID, review.BuyerID) + assert.Equal(t, 5, review.Rating) + assert.Equal(t, "Great product!", review.Comment) + assert.NotEqual(t, uuid.Nil, review.ID) +} + +func TestCreateReview_InvalidRating(t *testing.T) { + db := setupReviewTestDB(t) + logger := zap.NewNop() + svc := NewService(db, logger, nil) + + productID := uuid.New() + sellerID := uuid.New() + require.NoError(t, db.Create(&models.User{ID: uuid.New()}).Error) + require.NoError(t, db.Create(&models.User{ID: sellerID}).Error) + require.NoError(t, db.Create(&models.Track{ID: uuid.New(), UserID: sellerID, FilePath: "/t.mp3"}).Error) + require.NoError(t, db.Create(&Product{ + ID: productID, + SellerID: sellerID, + Title: "P", + Price: 1, + ProductType: "track", + Status: ProductStatusActive, + }).Error) + + _, err := svc.CreateReview(context.Background(), productID, uuid.New(), 0, "bad") + require.Error(t, err) + assert.Contains(t, err.Error(), "rating must be between 1 and 5") + + _, err = svc.CreateReview(context.Background(), productID, uuid.New(), 6, "bad") + require.Error(t, err) + assert.Contains(t, err.Error(), "rating must be between 1 and 5") +} + +func TestCreateReview_NotPurchased(t *testing.T) { + db := setupReviewTestDB(t) + logger := zap.NewNop() + svc := NewService(db, logger, nil) + + buyerID := uuid.New() + sellerID := uuid.New() + trackID := uuid.New() + productID := 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(&Product{ + ID: productID, + SellerID: sellerID, + Title: "P", + Price: 1, + ProductType: "track", + TrackID: &trackID, + Status: ProductStatusActive, + }).Error) + // No order, no license for this buyer + + _, err := svc.CreateReview(context.Background(), productID, buyerID, 5, "Great!") + require.Error(t, err) + assert.ErrorIs(t, err, ErrReviewNotPurchased) +} + +func TestCreateReview_DuplicateReview(t *testing.T) { + db := setupReviewTestDB(t) + logger := zap.NewNop() + svc := NewService(db, logger, nil) + + 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(&Product{ + ID: productID, + SellerID: sellerID, + Title: "P", + Price: 1, + ProductType: "track", + TrackID: &trackID, + Status: ProductStatusActive, + }).Error) + require.NoError(t, db.Create(&Order{ID: orderID, BuyerID: buyerID, Status: "completed", TotalAmount: 1, Currency: "EUR"}).Error) + require.NoError(t, db.Create(&OrderItem{ID: uuid.New(), OrderID: orderID, ProductID: productID, Price: 1}).Error) + require.NoError(t, db.Create(&License{BuyerID: buyerID, TrackID: trackID, ProductID: productID, OrderID: orderID, Type: LicenseBasic}).Error) + + _, err := svc.CreateReview(context.Background(), productID, buyerID, 5, "First") + require.NoError(t, err) + + _, err = svc.CreateReview(context.Background(), productID, buyerID, 4, "Second") + require.Error(t, err) + assert.ErrorIs(t, err, ErrReviewAlreadyExists) +} + +func TestListReviews_Paginated(t *testing.T) { + db := setupReviewTestDB(t) + logger := zap.NewNop() + svc := NewService(db, logger, nil) + + productID := uuid.New() + buyer1 := uuid.New() + buyer2 := uuid.New() + buyer3 := uuid.New() + order1 := uuid.New() + order2 := uuid.New() + order3 := uuid.New() + + require.NoError(t, db.Create(&models.User{ID: buyer1}).Error) + require.NoError(t, db.Create(&models.User{ID: buyer2}).Error) + require.NoError(t, db.Create(&models.User{ID: buyer3}).Error) + require.NoError(t, db.Create(&Product{ID: productID, SellerID: uuid.New(), Title: "P", Price: 1, ProductType: "track", Status: ProductStatusActive}).Error) + require.NoError(t, db.Create(&Order{ID: order1, BuyerID: buyer1, Status: "completed", TotalAmount: 1, Currency: "EUR"}).Error) + require.NoError(t, db.Create(&Order{ID: order2, BuyerID: buyer2, Status: "completed", TotalAmount: 1, Currency: "EUR"}).Error) + require.NoError(t, db.Create(&Order{ID: order3, BuyerID: buyer3, Status: "completed", TotalAmount: 1, Currency: "EUR"}).Error) + + require.NoError(t, db.Create(&ProductReview{ProductID: productID, BuyerID: buyer1, OrderID: order1, Rating: 5, Comment: "A"}).Error) + require.NoError(t, db.Create(&ProductReview{ProductID: productID, BuyerID: buyer2, OrderID: order2, Rating: 4, Comment: "B"}).Error) + require.NoError(t, db.Create(&ProductReview{ProductID: productID, BuyerID: buyer3, OrderID: order3, Rating: 3, Comment: "C"}).Error) + + reviews, err := svc.ListReviews(context.Background(), productID, 2, 0) + require.NoError(t, err) + require.Len(t, reviews, 2) + // Order is created_at DESC; C created last so first in result + assert.Equal(t, 3, reviews[0].Rating) + assert.Equal(t, 4, reviews[1].Rating) +} + +func TestListReviews_EmptyProduct(t *testing.T) { + db := setupReviewTestDB(t) + logger := zap.NewNop() + svc := NewService(db, logger, nil) + + productID := uuid.New() + require.NoError(t, db.Create(&Product{ID: productID, SellerID: uuid.New(), Title: "P", Price: 1, ProductType: "track", Status: ProductStatusActive}).Error) + + reviews, err := svc.ListReviews(context.Background(), productID, 20, 0) + require.NoError(t, err) + require.Empty(t, reviews) +}