Files
projet_gestion_commande/backend/gestion/tests/rewards_handler_test.go
T
Xor290 46652bb925
Backend - Build & Lint / build (push) Failing after 30m2s
Frontend Client - EAS Build / build (push) Canceled after 7m55s
Frontend Web - Build & Lint / build (push) Failing after 10m18s
chore: build
2026-08-21 13:08:56 +02:00

178 lines
6.9 KiB
Go

package tests
import (
"bytes"
"encoding/json"
"gestion/db"
"gestion/handlers"
"gestion/models"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func claimRewardContext(username string, body []byte) (*gin.Context, *httptest.ResponseRecorder) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/points/claim", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = req
c.Set("database", testDB)
c.Set("username", username)
return c, rec
}
// configureRewardSettings applique la récompense donnée, avec pool_0 mappé
// sur la catégorie "test" — nécessaire pour que eligibleRewardProducts
// (qui croise pool.Categories et reward.CategoryConfigs) considère les
// reward_items comme éligibles.
func configureRewardSettings(t *testing.T, reward *models.PointsReward) {
t.Helper()
settings := db.DefaultSettings()
settings.PointsPools[0].Categories = []string{"test"}
settings.PointsReward = reward
if err := testDB.UpdateSettings(settings); err != nil {
t.Fatalf("UpdateSettings: %v", err)
}
}
// Flux complet réel : POST /points/claim avec un seuil atteint doit ajouter
// le produit récompense configuré au panier et décompter la récompense.
func TestClaimMyReward_HTTPFlow_AddsRewardToBasketAndDecrementsAvailable(t *testing.T) {
cleanupStockTestData(t)
username := newTestClient(t, "reward_http_flow")
rewardProductID := newTestProduct(t, "RewardHTTPFlow", 5)
configureRewardSettings(t, &models.PointsReward{
Threshold: 20,
Description: "Un produit offert",
CategoryConfigs: []models.RewardCategoryConfig{{Category: "test", Type: "free_product", AllProducts: true}},
RewardItems: []models.RewardItem{{ProductID: rewardProductID, Quantity: 1, Price: 12}},
})
setClientPoolPoints(t, username, "pool_0", 20)
body, _ := json.Marshal(map[string]string{"pool_key": "pool_0"})
c, rec := claimRewardContext(username, body)
handlers.ClaimMyReward(c)
if rec.Code != http.StatusOK {
t.Fatalf("status HTTP: got=%d body=%s", rec.Code, rec.Body.String())
}
var resp struct {
Success bool `json:"success"`
RemainingRewards int `json:"remaining_rewards"`
ProductAdded bool `json:"product_added"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("décodage réponse: %v body=%s", err, rec.Body.String())
}
if !resp.Success || !resp.ProductAdded {
t.Fatalf("réclamation devrait réussir avec produit ajouté: %+v", resp)
}
if resp.RemainingRewards != 0 {
t.Errorf("remaining_rewards: got=%d want=0", resp.RemainingRewards)
}
rows := basketRewardItems(t, username)
if len(rows) != 1 || rows[0].ProductID != rewardProductID {
t.Errorf("le produit récompense doit être dans le panier: %+v", rows)
}
if rows[0].Price != 0 {
t.Errorf("catégorie free_product: le prix en panier doit être 0: got=%.2f", rows[0].Price)
}
}
// Catégorie configurée en "half_price_product" : le produit récompense doit
// être ajouté au panier à 50% du prix catalogue actif (pas 0€, pas le prix
// indicatif RewardItem.Price saisi par l'admin).
func TestClaimMyReward_HTTPFlow_HalfPriceCategoryChargesFiftyPercentOfCatalogPrice(t *testing.T) {
cleanupStockTestData(t)
username := newTestClient(t, "reward_http_halfprice")
rewardProductID := newTestProduct(t, "RewardHTTPHalfPrice", 5)
// newTestProduct crée un prix actif de 10.00€ pour quantity=1 (voir tests/main_test.go).
configureRewardSettings(t, &models.PointsReward{
Threshold: 20,
Description: "Un produit à moitié prix",
CategoryConfigs: []models.RewardCategoryConfig{{Category: "test", Type: "half_price_product", AllProducts: true}},
RewardItems: []models.RewardItem{{ProductID: rewardProductID, Quantity: 1, Price: 999}}, // Price indicatif, doit être ignoré
})
setClientPoolPoints(t, username, "pool_0", 20)
body, _ := json.Marshal(map[string]string{"pool_key": "pool_0"})
c, rec := claimRewardContext(username, body)
handlers.ClaimMyReward(c)
if rec.Code != http.StatusOK {
t.Fatalf("status HTTP: got=%d body=%s", rec.Code, rec.Body.String())
}
rows := basketRewardItems(t, username)
if len(rows) != 1 || rows[0].ProductID != rewardProductID {
t.Fatalf("le produit récompense doit être dans le panier: %+v", rows)
}
if rows[0].Price != 5.0 {
t.Errorf("catégorie half_price_product: prix attendu = 50%% de 10.00€ = 5.00€: got=%.2f", rows[0].Price)
}
}
func TestClaimMyReward_HTTPFlow_RejectsWhenBelowThreshold(t *testing.T) {
cleanupStockTestData(t)
username := newTestClient(t, "reward_http_below")
rewardProductID := newTestProduct(t, "RewardHTTPBelow", 5)
configureRewardSettings(t, &models.PointsReward{
Threshold: 20,
CategoryConfigs: []models.RewardCategoryConfig{{Category: "test", Type: "free_product", AllProducts: true}},
RewardItems: []models.RewardItem{{ProductID: rewardProductID, Quantity: 1, Price: 12}},
})
setClientPoolPoints(t, username, "pool_0", 5)
body, _ := json.Marshal(map[string]string{"pool_key": "pool_0"})
c, rec := claimRewardContext(username, body)
handlers.ClaimMyReward(c)
if rec.Code != http.StatusConflict {
t.Fatalf("status HTTP: got=%d want=%d body=%s", rec.Code, http.StatusConflict, rec.Body.String())
}
}
// Si un item récompense configuré par l'admin pointe vers un produit
// supprimé/inexistant, la réclamation entière doit échouer — la récompense
// ne doit pas être consommée sans qu'aucun produit ne soit livré au client
// (ClaimPoolReward + AddRewardsToBasket sont maintenant dans la même
// transaction via ClaimPoolRewardAndAddToBasket).
func TestClaimMyReward_HTTPFlow_FailsAtomicallyWhenProductMissing(t *testing.T) {
cleanupStockTestData(t)
username := newTestClient(t, "reward_http_missing_product")
configureRewardSettings(t, &models.PointsReward{
Threshold: 20,
// ProductIDs explicite (pas AllProducts) : le produit n'existe pas en
// base, donc il n'apparaîtrait jamais dans productCategories et ne
// serait jamais éligible via une correspondance AllProducts.
CategoryConfigs: []models.RewardCategoryConfig{{Category: "test", Type: "free_product", ProductIDs: []int{999999999}}},
RewardItems: []models.RewardItem{{ProductID: 999999999, Quantity: 1, Price: 12}}, // produit inexistant
})
setClientPoolPoints(t, username, "pool_0", 20)
body, _ := json.Marshal(map[string]string{"pool_key": "pool_0"})
c, rec := claimRewardContext(username, body)
handlers.ClaimMyReward(c)
if rec.Code == http.StatusOK {
t.Fatalf("la réclamation ne doit pas réussir avec un produit récompense invalide, body=%s", rec.Body.String())
}
_, redeemed, err := testDB.GetClientPointsAndRewards(username)
if err != nil {
t.Fatalf("GetClientPointsAndRewards: %v", err)
}
if redeemed["pool_0"] != 0 {
t.Errorf("la récompense ne doit PAS être consommée si le produit est introuvable: got redeemed=%d want=0", redeemed["pool_0"])
}
}