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 } func configureRewardSettings(t *testing.T, reward *models.PointsReward) { t.Helper() settings := db.DefaultSettings() 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, Type: "free_product", Description: "Un produit offert", 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) } } 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, Type: "free_product", 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, Type: "free_product", 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"]) } }